From 419ecfb9e7566588bfd091e9edcfc9afaf7a38eb Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Mon, 11 Apr 2011 19:26:26 +0000 Subject: [PATCH] Merged V3.4-TEAM to HEAD 24882: Branch for Team 24883: Version Edition label 24906: Partial implementation of ALF-6566. Replace gears image... This check-in adds support for the feature as described in JIRA. However we do not yet have the icon graphics files. I have therefore used some temporary icons in order to flush out implementation issues and to enable browser-based testing. A future check-in will add the correct placeholder icons and if they follow the naming convention, no change in code should be necessary. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also added and modified some test cases. 24909: ALF-6606: Configurable sorting for Share Document Library. Also change Show/Hide Folder button to icon. (Placeholder icons) 24914: ALF-6564: Initial cut of JMXFormProcessor, will show all attributes of a given MBean and has the ability to persist an attribute value. 24916: Initial Alfresco Team project structure and build targets 24917: First tranche of thumbnail icons for ALF-6566. Replace gears icon. Have added icons for some basic mime types and removed some temporary icons. I had to do a clean repo build to flush out the temporary docx and jpeg icons. 24919: svn ignore patterns 24922: Missed files for team build targets 24932: Add a simple AVM to Zip exporter service, initially for use by the site exporter 24935: Start on a java backed webscript to export a site, including all the details (ALF-6567) 24945: Removed two unnecessary TODOs from the RatingService code. 24949: Improved documentation for RatingService webscripts. Added sample JSON responses for two webscripts. 24956: Implementation of ALF-6792. RatingSchemes should allow self-rating. Formerly, the cm:creator of a node could never apply a rating to it. An unchecked RatingServiceException would be thrown if they attempted to do so. With this check-in ratingScheme beans have a new property: selfRatingAllowed. The exception will now only be thrown if a cm:creator attempts to rate their own content in a scheme where this property is false. The property is true by default and in particular, is true for "likesRatingScheme". 24958: ALF-6599: First pass DND file upload - FireFox4/Chrome support for single/multifile DND upload with basic progress 24961: Added additional data to the response coming from rating.post. I have added averageRating, ratingsTotal and ratingsCount to that response. 24963: ALF-6625: Balloon Popup Framework. Initial checking. More work maybe required when we start using it, or when designs are complete. 24964: ALF-6598 "Configure Site page refactor" - page.lib.js - Freemarker lib to sort out used pages and which links and labels to use - Drag n drop * now supports horisontal lists * improved tabfocus and keynavigations * callbacks can be attached on dnd events: delete-clicks, enter-clicks, element-moved, element-duplicated - Alfresco.util.isVisible - Checks if the element and all its parents are visible and displayed in the ui. - Alfresco.util.PopupManager.getUserInput now accepts a "input" parameter that can be set to "text" ("textarea" is default) - Automatic click listeners can be defined as: ${msg("link.rename")} which will call: onRenameClick: function(pageId, anchor) {} 24965: Changing date format of appliedAt JSON property to xmldate. 24971: ALF-6600: Event handling performance improvements for DND file upload 24976: Added team.war to continuous assemble-tomcat 24977: ALF-6600: Initial pass at DND upload highlighting 24979: Add method AuthorityService.getAuthorityNodeRef, and unit tests for it 24980: UI label change for Team install 24981: Set security permission on the new AuthorityService.getAuthorityNodeRef method, in line with the other get methods 24987: ALF-6607 - Likes, favourite and comment actions. Also DocLib panel redesign. Updates to Rating Service to better match Team use cases. 24989: ALF-6601: Added JavaScript multipart data constructor for FireFox 3.6 support 24990: Fix "Access Denied" error when navigating into a "Liked" folder. 25000: Merged V3.4 to V3.4-TEAM 24999: ALF-6764 - Updated copyright year to 2011 25004: ALF-6601: Set file size limit, HTTP status code error handling 25007: Add user, group and person ACP exports to the Site Export webscript. (The user export is provisional, pending a service level way to access the user NodeRef) 25008: ALF-6788: Update dashboard template to provide full width component and presets updates to reflect new default layouts. 25017: Initial check-in for ALF-6809. A service to managed deleted items. This check in adds a basic REST API for GETting and DELETEing deleted items from the archive store. JUnit tests of the REST API are included but are not yet complete. 25018: Trivial fix for failing test case related to ALF-6809 25019: A previous check-in (25017) accidentally included an extra Java file that wasn't ready. File is related to ALF-6809 and will be implemented today. This check-in removes the internals of the class to allow it to compile. The class is not injected into the spring application context and is essentially dead code pending its implementation later. 25027: Adding nodeType to the archivednodes.get webscript response. Requested by UI as part of ALF-6809. 25035: ALF-6789: Personal dashboard welcome (with wireframe styling) 25036: ALF-6782 - Undelete administration console page (WIP) ALF-6784, ALF-6786, ALF-6787 25037: ALF-6564: Added operations to default form, all parameter-less operations are displayed as a button on the form. Clicking a button executes the operation after saving all attribute values. 25038: Added a node filter to the archivednodes.get webscript. Currently excluding cm:thumbnails. A new bean on the script's controller class that allows injection of nodetypes to be excluded. Should be trivial to exclude other node types later and easy to exclude based on other criteria. 25039: Part of ALF-6809. Deleted items were returned from archivednodes.get in the wrong order. They are now sorted by archive datetime most recent first. (Was previously sorted with most recent last.) Also switched the test case, which was backwards too! 25040: Changing some copyright headers from 2010 to 2011. 25041: ALF-6564: JMX forms can now be configured with selective attributes and the set of operations. Also includes an example form configuration for the outbound email almost matching Linton's wireframes. 25042: ALF-6788: Fix rendering of old (V3.4) user dashboards stored in AVM with new Team layout 25044: Part of ALF-6809. Added displayPath value to archivednodes.get. 25047: Part of ALF-6809. Reconfiguration of cm:thumbnail archive behaviour. Formerly, we were archiving cm:thumbnails on deletion and filtering them from the archivednodes.get webscript which was not ideal as they remained in the archive. This check in removes the filter on the GET webscript and prevents cm:thumbnails from being archived in the first place. 25049: ALF-6782 - added Trashcan to admin tools menu - cleaned up list, show display path 25050: ALF-6782 - css tweaks 25052: Added check for multiple calls to onHistoryManagerReady() as workaround for issue with multiple History Manager objects when using Alfresco.util.DataTable. 25053: Test case refactoring as part of ALF-6809 25055: ALF-6803: First drop of aggregate file upload progress information. 25056: Part of ALF-6809. A preliminary PUT webscript that is used to restore nodes from the archive to their original loaction. Currently only works on a single node per call (which is what the JIRA requires). Test code currently incomplete. I'm checking in now to give UI something to work with. 25057: Fix for failing test case, which was part of ALF-6566. 25058: ALF-6567 - AVM importer, which can load a zip file's contents into a specified AVM filesystem. Includes a bootstrap wrapper around the main importer. 25068: Part ALF-6893: RTEAM 03: Expose restrictions using RepoAdminService - Subtask of ALF-6832: AT17: License restriction reporting - Added RepoAdminService.getRestrictions in Java and Webscripts APIs - Currently nulls returned indication 'no restrictions' - Cleaned up unused AdminService 25069: ALF-6625 - adds a switch to control what calendar views are enabled and switches off Day, Week and Month views in Team. 25073: Test case overhaul and minor tidying of code. Part of ALF-6809. 25074: ALF-6803: Style updates to upload progress info 25075: ALF-6601: Updated in-memory file upload check so that failure is based on total (not individual) file size. 25081: Merged V3.4 to V3.4-TEAM 25051: Build Fix: ALF-6865 CopyServiceImplTest.testCopyToNewNodeWithPermissions failing on permission copying 25082: Part ALF-6893: RTEAM 03: Expose restrictions using RepoAdminService - Subtask of ALF-6832: AT17: License restriction reporting - Added RepoAdminService.getUsage in Java and Webscripts APIs 25084: Improve how the Site Exporter gets at the users for a site, and have the user authentication details export skipped if a non-repository based authenticator is enabled 25085: Final part of ALF-6809. Support for paging the results from archivednodes.get. Follows the standard maxItems, skipCount convention and enables paging in the trashcan view automatically thanks to Kev's use of the convention. 25088: Final, final part of ALF-6809. Throwing 4xx, 5xx exceptions for nodeArchiveService-level failures to restore nodes 25090: ALF-6601: Updated in-memory upload limit to be configurable 25096: ALF-6601: Updates to DND highlighting and behaviour, set correct upload limit in bytes, NLS updates 25101: Fixing failing test cases. Fallout from recent changes to the JSON response formats in these webscripts. 25103: ALF-6613 - SpringSurf improvements to allow easier refactoring of Document Details page - latest SpringSurf libs with RequestCachingConnector improvements - removed manual request level caching of remote calls responses in web-tier components - now completely automatic 25104: ALF-6803: DND upload dialog styling 25106: ALF-6802: Added feature detection to disable drag'n'drop events (disables for IE6, IE7 & IE8) 25108: ALF-6564: Added a check to the JMXFormProcessor to ensure the current user is an administrator. Also relaxed the rules in the checkbox control so it can be used for string values not just boolean values. 25113: ALF-6789: Updated CSS to support IE6/IE7 25116: Prevent server-side exception when navigating trashcan; related to paging. (ALF-6809) 25117: ALF-6824 - Only show "Like" and "Comment" actions if a user has the correct permissions. Additional fixes for IE7. 25118: Fix incorrect category aspect name. Missing file from r25117 25119: ALF-6645 - Share and Team branding updates 25121: Removed erroneous comment block 25123: Merged HEAD to BRANCHES/V3.4-TEAM: 25115: Fixes: ALF-6336 - resolved incorrectly translated date formatting strings. 25124: Add a new bootstrap component for bootstrapping Sites. Handles the contents, AVM and authentication, people and their group membership to follow. (Uses singleton spring beans in line with the patch service's use, to ensure that when loaded from a bootstrap extension things still occur in the correct order) 25128: Merged V3.4 to V3.4-TEAM 25127: Merged V3.3 to V3.4 25126: ALF-6903 - Share theme feature does not work. Also fixes issue with MultiThreadedHttpConnectionManager in SpringSurf. 25130: Continue with the site bootstrapper - finish supporting the loading of the site contents ACP 25132: Work on ALF-6832:TR25: License restriction reporting - ALF-6893: RTEAM 03: Expose restrictions using RepoAdminService - ALF-6911: RTEAM 02: Record and expose system attributes - Added RepoUsageComponent - Unit test incl. testLicenseUse - Persists and retrieves usage data using AttributeService 25133: ALF-6789: Added "Close" link to dynamic-welcome dashlet and associated webscript 25135: ALF-6789: Add missing localization 25136: ALF-6601: Defensively code against missing config 25137: Fix up of transfer test. 25138: Flattening of user preferences remote calls - ensures /preferences hits the RequestCachingConnector - reduces no. of remote calls by 3 for the doclib and by 4 for a site dashboard. 25139: ALF-6804: Disable drop outside of document list 25140: ALF-6887 - for sending HTML emails - prelim support - either text explicitly starts with " share.protocol, share.host, share.port, share.context (note: you may need to override for dev/demo env) 25178: ALF-6564: TR12: Implement JMX Form Processor. Added tests, fixed a couple of bugs in form processor and added all Mike's form config for MBeans we're provisionally exposing. 25187: ALF-6998 - activities feed email - quick fix if running within repo-only context (eg. via Eclipse) 25188: Prevented NPE with Enterprise edition built as community. 25189: Expose getAllowWrite on the TransactionService API - This is a user-independent flag as opposed to isReadOnly, which makes allowance for the 'System' user 25190: Work on ALF-6832:TR25: License restriction reporting - ALF-6893: RTEAM 03: Expose restrictions using RepoAdminService - Use TransactionService as the source of 'isReadOnly' 25191: Fix to issue where null could be passed as URI Tokens - fixes issue where pages failed to render on first display since rev 25166 25192: CSS tweak 25194: I18N'd system startup message 25195: Restrictions now sets current time and readOnly property. 25196: ALF-6790: Added user prefs to enable site welcome dashlet visibility 25198: ALF-6790: Fix user close welcome dashlet persistence. Added site welcome dashlet close reload. 25200: ALF-6803: Update upload dialog title to display (encoded) folder name (not entire path) 25203: ALF-6834 - TR15: Activities feed email notifications - ALF-6931 - RTEAM 23: update activity feed DAO to filter by min id and/or max items - ALF-6929 - RTEAM 21: now uses last feed id for each user - minor cleanup (eg. tweak template to deal with null message) 25204: ALF-6790: Fixed broken site preference logic 25206: Checkpoint ALF-6608 - Inline property editing - name field. Also paves the way for ALF-6611 - Inline property editing - any meta field. Currently does not constrain input values, nor handle repository errors (e.g. duplicate name). 25212: ALF-6602: Updates to reflect latest designs 25213: ALF-6564: More MBean form config 25248: A new schema version range for 3.5 25249: ALF-7049 - RTEAM 12: Disable Alfresco Explorer 25252: ALF-7050 - RTEAM 09: Disable MT 25263: Fix trailing comma for IE browsers 25268: Implementation of ALF-6829 Popularity of a node (RatingService). This check-in is actually a fairly generic support for rolled up property values within rating schemes. Popularity is currently the only concrete rollup in the system, but it should be possible to add more without too much difficulty. Given the published API for the rating service, we have added rollups for "ratingCount" and "ratingTotal" for the built-in rating schemes (likes). Therefore searching/sorting on either cm:likesRatingSchemeCount or cm:likesRatingSchemeTotal, both of which are defined in an aspect cm:likesRatingSchemeRollups should give popularity for the likes rating scheme. Additions to the content model. I have added aspects, properties for the built-in rollups, namely count and total. Spring changes The rating scheme(s) now have rollups injected into them. These rollups are algorithm classes that know how to calculate the rolled up props. Naming conventions are used to determine the output property and are captured in RatingRollupNamingConventionsUtil.java. API changes New methods: RatingService.getRatingRollup(NodeRef, String, String) & RatingScheme.getPropertyRollups() Changes to the RatingService implementation and JUnit test code. To roll your own rating rollups: To add your own rating rollup, you need to reuse one of the existing AbstractRatingRollupAlgorithm subclasses or create a new one. That class will produce the value of the rolled up property by iterating the ratings in the given rating scheme for a given node. You need to inject your rollup algorithm into the rating scheme bean in rating-services-context.xml On applyRating() and removeRating() the RatingService will check the rating scheme for any registered rollup algorithms and will have them recalculated. It will then use the naming conventions described in the JIRA and in RatingRollupNamingConventionsUtil to get aspect/property names for each of the rollups. It will then add the property value in the normal way. Therefore, you need to extend the content model to include the expected aspect and property values. Follow likesRatingScheme for a sample. 25273: Fix failing unit test. 25274: DocLib sorting support for "popularity" (Likes rating scheme) 25277: ALF-6602: Updated instructions to reflect new design. Info shown now dynamically based on site ownership/access rights/browser feature support 25278: Merged BRANCHES/V3.4 to BRANCHES/V3.4-TEAM: 25267: ALF-1070 - if site/user is deleted then immediately clean associated site/user feed 25279: ALF-6601: Updated to handle 0 byte dropped files (or folders) 25280: ALF-7009 WIP checkpoint - Also removed some code that is obsolete since ConsoleTool class extends Alfresco.component.Base 25286: ALF-6599: Changed folder DND target back to image (not row) 25298: ALF-7009 - Upload and replace Share logo 25311: ALF-7080 / ALF-7002 - ActivitiesFeed subsystem (+ option to enable/disable job triggers) - moved ActivitiesFeed into subsystem for dynamic mgmt via JMX - FeedNotifier repeat interval can be dynamically changed - also added "enabled" property to AbstractTriggerBean => FeedNotifier job can be disabled / re-enabled 25312: ROLLBACK (Partial) 25249 ALF-7049 Disable Alfresco Explorer. 25313: ROLLBACK 25252 : ALF-7050 - RTEAM 09: Disable MT 25317: ALF-6930 - bootstrap / patch activities email template 25331: ALF-6890 - RTEAM 16: License-based restrictions: Number of users 25333: Merged BRANCHES/V3.4 to BRANCHES/V3.4-TEAM: 25319: Build/test fix (fallout from ALF-1070) 25322: Build/test fix (fallout from ALF-1070) 25356: ALF-6930 - bootstrap / patch activities email template - missed file, sorry ! 25358: ALF-6834 - Activities email: tweaks - pass through repeatInterval (to template model) - add exclude email list (eg. for default admin) 25360: RTEAM 28: License restriction reporting: Force refresh - Added API for selective updates of usage data based on an enumeration: USAGE_USERS, USAGE_DOCUMENTS or USAGE_ALL - When people are added, for instance, usage will be updated and then retrieved for checking. 25363: ALF-6639 - Default collaboration dashboard - NOTE: may need Team overlay version on merge if this change is *not* the default for Swift 25364: Modified usage/restriction admin webscripts to use a common FTL lib for the json output and added some missing quotes around the license mode value. 25365: ALF-6597: Added DashletTitleBarActions widget and applied to WebView dashlet 25367: ALF-7082 - Remove Network Dashlet - Removed network dashlet from codeline - Support in WebScripts to allow hook for override of exception handling from webscripts for specific use cases - Surf LocalWebScriptRuntime overrides error handling looking for specific case of SC_NOT_FOUND - and silent ignores missing webscript components - Improved Share handling of missing webscript components that have already been bound into a dashboard (i.e. not a missing "slot" but an existing component binding that points to a missing webscript URL) - this will also allow for easier removal of other existing dashlets in the future without requiring repo-side patches or similar. 25369: ALF-7042 (ALF-6832) RTEAM 28: License restriction reporting: Force refresh - Added unit test for update WebScript 25375: Implementation of ALF-7024. Document versions service/webscript needs to return avatar url. The avatar url has been added to the JSON response as requested. If there is no avatar, a JSON null is returned as requested. 25377: Add maxDocs, maxUsers and license mode to License JMX bean 25378: Adding additional property to version.get webscript: a correctly-formatted ISO8601 date. This has been added as requested in the comments of ALF-7024. 25381: Refactor of LicenseComponent and related to produce LicenseMode in descriptor (ALF-6907 RTEAM 19) - Need actual enum to do later usage updates according to mode 25382: ALF-7053 - RTEAM 07: Disable Transfer 25387: ALF-6564: Finished JMXFormProcessor (again). There is now a configurable list of operations to ignore, revert is ignored by default. The labels for the buttons are also localisable now. 25390: Fixed up License MBean after changing the descriptor API 25391: ALF-6911: RTEAM 02: Record and expose system attributes - Added job locking around individual usage queries - It is possible to concurrently update user and document counts - Exposed true/false return value on updates and added this to usages webscripts 25392: Switched version edition back to Community - If we distribute Team using a community build, then we should know about it. - Team functionality is triggered by an Enterprise build or TEAM license. 25393: Undid rev 25392 25394: Work on ALF-6832:TR25: License restriction reporting - ALF-6893: RTEAM 03: Expose restrictions using RepoAdminService - ALF-6911: RTEAM 02: Record and expose system attributes - Added RepoUsageMonitor - Self-starting schedule - Only checks for restrictions that are in place - Issues warnings and errors; puts system into read-only mode on violation 25403: ALF-6890 - Switch over of user query to database backed query. 25408: WIP checkpoint for Agenda view refactor: - Adds Alfresco.util.friendlyDate to supply "Yesterday/Today/Tomorrow" style date - refactors getEvents into a common function in calendar-view.js (calendar-view-month not touched because it gets elements from the DOM rather than API) - Uses DataTables to display agenda events (currently unformatted) 25410: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/V3.4-TEAM: 25409: Merged BRANCHES/V3.4 to BRANCHES/DEV/V3.4-BUG-FIX: 25407: Merged BRANCHES/V3.3 to BRANCHES/V3.4: 25401: Allow continuous.xml database drop/create on postgresql and mysql to support the database being on a different machine 25406: We no longer need a 2nd _test database for the unit tests, so remove the code that created/removed them during the continuous build 25411: Fix to allow application logo to be uploaded with same filename as a previous logo. 25416: Implementation of ALF-7100. Comments webscript doesn't return dates in iso8601. I added iso8601-formatted date fields jsut as for ALF-7024. 25418: Implementation of ALF-7173. Remove the RatingService restriction whereby a single user can only rate a single node with one RatingScheme. Change to the RatingService javadoc so that it doesn't mention the restriction. Additional method in RatingService: getRatingsByCurrentUser - to return multiple ratings. Reimpl's testOneUserRatesInTwoSchemes test case from a -ve to a +ve test case. Rewrote the REST test cases slightly to cover the case of a single user multiply rating a node. Various changes through the apply() case to support this. 25419: Change the Site Loading for ALF-6567 from a bootstrap to a patch. This means that if a site is loaded then deleted, it won't be re-loaded again. 25420: Fixed unit test for ALF-6911: RTEAM 02: Record and expose system attributes - Forgot to prompt user usage changes to check numbers against 25422: Addition of selfRatingAllowed field to ratingdefinitions.get webscript. As this is now a configurable property on a scheme, it should be reflected in the REST API. 25423: Node rows that transition to the deleted state (not archive) are given type sys:deleted 25425: Add an example extension context file to patch-load an exported Site, and allow the import path to use defaults to reduce the number of settings required 25426: ALF-6597: Added temporary tooltip code for DashletToolbarActions (and updated to WebView dashlet to use it) 25427: ALF-6789: Removed dynamic welcome dashlet from add dashlets menu 25428: ALF-6599: Removed encoding of spaces in displayed location name on DND upload to folder 25430: ALF-6890 - tweaks to query and person service for user limit stuff. 25432: ALF-6597: Updated DashletToolBarActions to ensure fade in on first mouseover 25433: Implemented ALF-6613, ALF-6614, ALF-6615, ALF-6616, ALF-6617, ALF-6618, ALF-6619, ALF-6620, ALF-6621, ALF-6622 - PART #1 - ALF-6613 TR22: Document Details page improvements - ALF-6614 Page redesign and refactor * Components are atomic and doesn't need global events to work. - ALF-6615 Document Actions panel - ALF-6616 Tags panel - NEW, replaces the old "info (tags+permissions)" component - ALF-6617 Share links panel - ALF-6618 Properties panel - ALF-6619 Permissions panel - NEW, replaces the old "info (tags+permissions)" component - ALF-6620 Workflows panel - ALF-6621 Version History panel - ALF-6622 Comment component redesign and refactor - alfresco-macros.lib.ftl * <#function uriTemplate id> Helper for getting a uriTemplate in freemarker * <#function userProfileLink> Helper fopr rendering a userProfile link in freemarker - alfresco-util.js - Rhino Javascript-helpers * function error(code, message, redirect) Helper for redriting and throwing a webscript error * function param(name, defaultValue) Helper for placing a webscript "param" in the model, lloks for param in the following order args, page.templateArgs, properties, defaultValue. If no value is found and no default value was given an error is thrown. Useful to avoid webscripts "crashing" when accessed from /service/ rather than from /page/ path, i.e. when refreshed/reloaded using ajax. * function getRepositoryUrl() * function getRootNode() * function getDocumentDetails(nodeRef, site) - alfresco.js * New in Alfresco.util.DataTable c.dataSource.doBeforeParseData - to modify response before rendering, i.e. if an array is the respons rather than an object c.dataTable.config.className - if another css class than alfresco-datatable shall be used c.paginator.history - set to false to avoid browser history management to kick in reloadDataTable() - to reload getData(record) - to get data related to a row in the table * sanitizeMarkup() - moved out code from request(), strips out - anyFiles - copyToTarget - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/config/alfresco/hibernate-context.xml b/config/alfresco/hibernate-context.xml index 1ab65634dc..c1e25b1594 100644 --- a/config/alfresco/hibernate-context.xml +++ b/config/alfresco/hibernate-context.xml @@ -135,6 +135,9 @@ org/jbpm/taskmgmt/log/SwimlaneCreateLog.hbm.xml org/jbpm/taskmgmt/log/SwimlaneAssignLog.hbm.xml org/jbpm/job/CleanUpProcessJob.hbm.xml + + org/alfresco/repo/workflow/jbpm/jbpm.ext.queries.hbm.xml + diff --git a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml index 7be5713d5c..219a61fddd 100644 --- a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml +++ b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml @@ -6,11 +6,13 @@ - @@ -40,4 +42,8 @@ + + + + \ No newline at end of file diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml index 7c8a036e76..dba33be27c 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml @@ -14,6 +14,8 @@ + + @@ -105,80 +107,76 @@ - - - - - - - + + delete from alf_activity_feed + where site_network = #siteNetwork# + + + + delete from alf_activity_feed + where feed_user_id = #feedUserId# + + select - ma.mkey tenantDomain, - a2.bool_value isEnabled, - a3.string_value rootDir + ma.mkey as tenantDomain, + a2.bool_value as isEnabled, + a3.string_value as rootDir from alf_global_attributes ga join alf_map_attribute_entries ma on (ma.map_id = ga.attribute) @@ -239,10 +239,10 @@ select - ma.mkey componentName, - ma2.mkey propName, - a2.string_value propValue + ma.mkey as componentName, + ma2.mkey as propName, + a2.string_value as propValue from alf_global_attributes ga join alf_map_attribute_entries ma on (ma.map_id = ga.attribute) @@ -275,9 +275,9 @@ + + select + count(*) + from + alf_mimetype + + where + mimetype_str = #mimetypeMatch# + mimetype_str like #mimetypeMatch# + + JUNKED + + + \ No newline at end of file diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-usages-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-usages-common-SqlMap.xml new file mode 100644 index 0000000000..1dbdddce29 --- /dev/null +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-usages-common-SqlMap.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/usage-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/usage-common-SqlMap.xml index b806c2b6c1..51898e3af3 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/usage-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/usage-common-SqlMap.xml @@ -170,21 +170,6 @@ p1.string_value, n.audit_creator - - - diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 05db9e0271..fe7e53e40f 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -55,6 +55,12 @@ + + + + + + @@ -181,6 +187,13 @@ + + + + + + + @@ -235,6 +248,30 @@ --> + + + + + + + + + + + + + + + + + + + + + ${server.transaction.allow-writes} + + + @@ -347,6 +384,7 @@ ${spaces.templates.email.invite.childname} ${spaces.templates.email.invite1.childname} ${spaces.templates.email.notify.childname} + ${spaces.templates.email.activities.childname} ${spaces.imapConfig.childname} ${spaces.transfers.childname} ${spaces.transfer_groups.childname} @@ -482,6 +520,11 @@ alfresco/templates/email_templates.acp alfresco/messages/bootstrap-spaces + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname} + alfresco/templates/activities-email-templates.acp + alfresco/messages/bootstrap-spaces + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.rss.childname} alfresco/templates/rss_templates.acp @@ -550,15 +593,9 @@ alfresco/bootstrap/alfrescoAuthorityStoreDefaultMembers.xml - - /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.scripts.childname} - alfresco/bootstrap/imapScripts.xml - alfresco/messages/bootstrap-imapScripts - - /${spaces.company_home.childname}/${spaces.dictionary.childname} - alfresco/bootstrap/imapSpaces.xml + alfresco/bootstrap/imapSpaces.acp alfresco/messages/bootstrap-spaces @@ -595,9 +632,41 @@ alfresco/bootstrap/scheduledActionsFolder.xml alfresco/messages/bootstrap-spaces + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}/${spaces.templates.email.invite.childname} + alfresco/templates/new-user-templates.acp + alfresco/messages/bootstrap-spaces + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}/${spaces.templates.email.invite.childname} + alfresco/templates/invite-email-templates.acp + alfresco/messages/bootstrap-spaces + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}/${spaces.templates.email.notify.childname} + alfresco/templates/email_templates2.acp + alfresco/messages/bootstrap-spaces + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.imapConfig.childname}/${spaces.imap_templates.childname} + alfresco/bootstrap/imapSpacesLocales.acp + alfresco/messages/bootstrap-spaces + + + + + sitestore:/ + + + diff --git a/config/alfresco/messages/action-config.properties b/config/alfresco/messages/action-config.properties index 71a0278add..b29c6324cd 100644 --- a/config/alfresco/messages/action-config.properties +++ b/config/alfresco/messages/action-config.properties @@ -121,6 +121,7 @@ mail.subject.display-label=Subject mail.text.display-label=Body mail.from.display-label=From mail.template.display-label=E-Mail template +mail.template_model.display-label=Additional parameters for the email template check-in.title=Check in check-in.description=This will check in the matched content. diff --git a/config/alfresco/messages/action-config_de.properties b/config/alfresco/messages/action-config_de.properties index 09b724e9ff..1441cc1439 100755 --- a/config/alfresco/messages/action-config_de.properties +++ b/config/alfresco/messages/action-config_de.properties @@ -28,7 +28,7 @@ in-category.title=Hat Kategorie in-category.description=Die Regel wird auf alle Elemente angewandt, die den angegebenen Kategoriewert aufweisen. in-category.category-aspect.display-label=Kategorieaspekt in-category.category-value.display-label=Kategoriewert - + is-subtype.title=Inhalt des Typs oder Subtyps is-subtype.description=Diese Regel wird auf alle Elemente angewandt, die zu einem spezifischen Typ oder dessen Subtypen geh\u00f6ren is-subtype.type.display-label=Typ @@ -55,7 +55,7 @@ compare-text-property.title=Eigenschaft mit Textwert compare-text-property.description=Eine Texteigenschaft der Metadaten, des Aspekts oder Typs vergleichen has-tag.title=Hat Tag -has-tag.description=Hat einen Tag, der auf einen Node angewandt wird. +has-tag.description=Hat einen Tag, der auf einen Knoten angewandt wird. has-tag.tag.display-label=Tag # Actions @@ -68,15 +68,15 @@ remove-features.title=Aspekt entfernen remove-features.description=Damit wird ein Aspekt aus dem passenden Element entfernt. remove-features.aspect-name.display-label=Aspekt -simple-workflow.title=Einfachen Workflow hinzuf\u00fcgen. +simple-workflow.title=Einfachen Workflow hinzuf\u00fcgen simple-workflow.description=Hiermit wird ein einfacher Workflow zu dem passenden Element hinzugef\u00fcgt. Damit kann das Element f\u00fcr den n\u00e4chsten Schritt in einem Workflow in einen anderen Raum verschoben werden. Wenn Sie einen Schritt ablehnen wollen, k\u00f6nnen Sie auch einen Raum angeben, in den verschoben werden soll. simple-workflow.approve-step.display-label=Schritt zustimmen -simple-workflow.approve-folder.display-label=Ordner Schrittt zustimmen +simple-workflow.approve-folder.display-label=Ordner Schritt zustimmen simple-workflow.approve-move.display-label=Verschieben zustimmen simple-workflow.reject-step.display-label=Schritt ablehnen simple-workflow.reject-folder.display-label=Ordner Schritt ablehnen simple-workflow.reject-move.display-label=Verschieben ablehnen - + link-category.title=Mit Kategorie verlinken link-category.description=Damit wird eine Kategorie auf das passende Element angewandt. link-category.category-aspect.display-label=Kategorieaspekt @@ -115,12 +115,13 @@ move.assoc-name.display-label=Zuordnungsname mail.title=E-Mail senden mail.description=Damit wird bei passendem Inhalt eine E-Mail an eine Liste von Benutzern geschickt. -mail.to.display-label=An -mail.to_many.display-label=An +mail.to.display-label=Zu +mail.to_many.display-label=Zu mail.subject.display-label=Betreff -mail.text.display-label=Textk\u00f6rper +mail.text.display-label=Body mail.from.display-label=Von -mail.template.display-label=E-Mail Mustervorlage +mail.template.display-label=E-Mail-Mustervorlage +mail.template_model.display-label=Zus\u00e4tzliche Parameter f\u00fcr die E-Mail-Mustervorlage check-in.title=Einchecken check-in.description=Damit wird der passende Inhalt eingecheckt. @@ -168,7 +169,7 @@ counter.counter=Eigenschaft des Z\u00e4hlers f\u00fcr das Element hochsetzen. execute-all-rules.title=Alle Regeln ausf\u00fchren execute-all-rules.description=Alle Regeln f\u00fcr die Kinder-Elemente ausf\u00fchren. -start-workflow.title=Arbeitsablauf beginnen +start-workflow.title=Workflow starten start-workflow.description=Damit wird der Arbeitsablauf f\u00fcr die passenden Elemente begonnen. start-workflow.workflowName.display-label=Name des Arbeitsablaufs start-workflow.endStartTask.display-label=Aufgabe @@ -177,35 +178,35 @@ start-workflow.startTaskTransition.display-label=\u00dcbergang # WCM Actions simple-avm-submit.title=Einfaches direktes Vorlegen -simple-avm-submit.description=Damit werden neuere Nodes im passenden Element im entsprechenden Staging vorgelegt. +simple-avm-submit.description=Damit werden neuere Knoten im passenden Element im entsprechenden Staging vorgelegt. simple-avm-promote.title=Einfache Sandbox Bef\u00f6rderung -simple-avm-promote.description=Damit werden neuere Nodes im passenden Element in die angegebene Ziel-Sandbox bef\u00f6rdert. -simple-avm-promote.target-store.display-label=Name des AVM-Zielspeichers +simple-avm-promote.description=Damit werden neuere Knoten im passenden Element in die angegebene Ziel-Sandbox bef\u00f6rdert. +simple-avm-promote.target-store.display-label=Name des AVM-Zielspeichers. -avm-revert-store.title=Einen Single Node in einem Speicher zur\u00fccksetzen. -avm-revert-store.description=Damit werden alle Nodes einschlie\u00dflich des und nach dem Argument-Node in eine vorherige Version zur\u00fcckgesetzt. +avm-revert-store.title=Einen Single Knoten in einem Speicher zur\u00fccksetzen. +avm-revert-store.description=Damit werden alle Knoten einschlie\u00dflich des und nach dem Argument-Knoten in eine vorherige Version zur\u00fcckgesetzt. avm-revert-store.version.display-label=Version, auf die zur\u00fcckgesetzt werden soll. -avm-revert-list.title=Eine Node-Liste in einem Speicher zur\u00fccksetzen. -avm-revert-list.description=Damit werden alle in der Liste enthaltenen Nodes zur\u00fcckgesetzt. +avm-revert-list.title=Eine Knoten-Liste in einem Speicher zur\u00fccksetzen. +avm-revert-list.description=Damit werden alle in der Liste enthaltenen Knoten zur\u00fcckgesetzt. avm-revert-list.version.display-label=Version, auf die zur\u00fcckgesetzt werden soll. -avm-revert-list.node-list.display-label=Die zur\u00fcckzusetzende, durch Strings codierte Node-Liste. +avm-revert-list.node-list.display-label=Die zur\u00fcckzusetzende, durch Strings codierte Knoten-Liste. avm-revert-list.flatten.display-label=Soll nach dem Zur\u00fccksetzen auf Stagingspeicher geebnet werden? avm-revert-list.store.display-label=Name des zur\u00fcckgesetzten Speichers, nur beim Ebnen erforderlich. avm-revert-list.staging.display-label=Name des Stagingspeichers, auf den geebnet werden soll. avm-revert-list.flatten-path.display-label=Relativer Speicher-Pfad, der geebnet werden soll. -avm-revert-to-version.title=Einen Node auf eine besondere Version zur\u00fccksetzen. -avm-revert-to-version.description=Damit wird ein Node auf eine bestimmte Version dieses Nodes zur\u00fcckgesetzt. -avm-revert-to-version.to-revert.display-label=AVM Node Deskriptor der Version, auf die zur\u00fcckgesetzt werden soll. +avm-revert-to-version.title=Einen Knoten auf eine besondere Version zur\u00fccksetzen. +avm-revert-to-version.description=Damit wird ein Knoten auf eine bestimmte Version dieses Knoten zur\u00fcckgesetzt. +avm-revert-to-version.to-revert.display-label=AVM Knoten Deskriptor der Version, auf die zur\u00fcckgesetzt werden soll. -avm-undo-list.title=Eine Node-Liste in einem Speicher erstellen, der f\u00fcr das Staging transparent ist. +avm-undo-list.title=Eine Knoten-Liste in einem Speicher erstellen, der f\u00fcr das Staging transparent ist. avm-undo-list.description=Das fungiert f\u00fcr die Sandbox eines Benutzers als L\u00f6scheinrichtung f\u00fcr Fehler. -avm-undo-list.node-list.display-label=Die zur\u00fcckzusetzende, durch Strings codierte Node-Liste. +avm-undo-list.node-list.display-label=Die zur\u00fcckzusetzende, durch Strings codierte Knoten-Liste. -avm-deploy-website.title=Eine Website auf einen Remote Server aufspielen. -avm-deploy-website.description=Damit wird eine Website auf einen Remote Server aufgespielt. +avm-deploy-website.title=Eine Site auf einen Remote Server aufspielen. +avm-deploy-website.description=Damit wird eine Site auf einen Remote Server aufgespielt. avm-deploy-website.webproject.display-label=NodeRef des Netzwerkprojektes, von dem das Deploy stammt. avm-deploy-website.server.display-label=NodeRef des Deployment Servers auf den aufgespielt werden soll. avm-deploy-website.attempt.display-label=NodeRef des Deployment-Versuches, zu dem dieses Deployment geh\u00f6rt. @@ -224,3 +225,4 @@ create-version.title=Erstellt neue Version create-version.description=Erstellt eine neue Version create-version.description.display-label=Versionsbeschreibung create-version.minor-change.display-label=Wesentliche \u00c4nderung + diff --git a/config/alfresco/messages/action-config_es.properties b/config/alfresco/messages/action-config_es.properties index 23074fa762..25b1b0760b 100755 --- a/config/alfresco/messages/action-config_es.properties +++ b/config/alfresco/messages/action-config_es.properties @@ -2,7 +2,7 @@ ac-compare-operations.equals=Igual a ac-compare-operations.contains=Contiene ac-compare-operations.begins=Comienza por -ac-compare-operations.ends=Termina en +ac-compare-operations.ends=Termina con ac-compare-operations.greater_than=Mayor que ac-compare-operations.greater_than_equal=Mayor o igual que ac-compare-operations.less_than=Menor que @@ -28,9 +28,9 @@ in-category.title=Tiene una categor\u00eda in-category.description=La regla se aplica a todos los elementos que tienen el valor de categor\u00eda especificado. in-category.category-aspect.display-label=Aspecto de categor\u00eda in-category.category-value.display-label=Valor de categor\u00eda - + is-subtype.title=Contenido del tipo o subtipo -is-subtype.description=La regla se aplica a todos los elementos que son de un tipo especificado o de sus subtipos. +is-subtype.description=La regla se aplica a todos los elementos que son de un tipo especificado o de sus subtipos is-subtype.type.display-label=Tipo has-aspect.title=Tiene el aspecto @@ -45,7 +45,7 @@ compare-mime-type.value.display-label=Tipo MIME composite-condition.title=Condici\u00f3n mixta composite-condition.description=Combinar varias condiciones para crear una condici\u00f3n m\u00e1s complicada. -compare-date-property.title=Propiedad con valor de fecha +compare-date-property.title=Propiedad con valor de fecha compare-date-property.description=Comparar una propiedad de fecha de metadatos, aspecto o tipo compare-integer-property.title=Propiedad con valor de n\u00famero @@ -76,7 +76,7 @@ simple-workflow.approve-move.display-label=Movimiento de aprobaci\u00f3n simple-workflow.reject-step.display-label=Paso de rechazo simple-workflow.reject-folder.display-label=Carpeta de paso de rechazo simple-workflow.reject-move.display-label=Movimiento de rechazo - + link-category.title=Enlace a categor\u00eda link-category.description=Esto aplicar\u00e1 una categor\u00eda al elemento coincidente. link-category.category-aspect.display-label=Aspecto de categor\u00eda @@ -115,12 +115,13 @@ move.assoc-name.display-label=Nombre de asociaci\u00f3n mail.title=Enviar email mail.description=Esto enviar\u00e1 un correo electr\u00f3nico a una lista de usuarios cuando coincida el contenido. -mail.to.display-label=A -mail.to_many.display-label=A +mail.to.display-label=Para +mail.to_many.display-label=Para mail.subject.display-label=Asunto mail.text.display-label=Cuerpo mail.from.display-label=Desde mail.template.display-label=Plantilla de email +mail.template_model.display-label=Par\u00e1metros adicionales para la plantilla de correo electr\u00f3nico check-in.title=Desbloquear check-in.description=Esto desbloquear\u00e1 el elemento coincidente. @@ -185,7 +186,7 @@ simple-avm-promote.target-store.display-label=El nombre del almac\u00e9n AVM de avm-revert-store.title=Restablecer un solo nodo en un almac\u00e9n. avm-revert-store.description=Esto restablece a una versi\u00f3n anterior todos los nodos incluidos y por debajo del nodo de argumento. -avm-revert-store.version.display-label=La versi\u00f3n a la cual restablecer. +avm-revert-store.version.display-label=Versi\u00f3n a la que se va a volver. avm-revert-list.title=Restablecer una lista de nodos en un almac\u00e9n. avm-revert-list.description=Esto restablece todos los nodos incluidos en la lista. @@ -224,3 +225,4 @@ create-version.title=Crea nueva versi\u00f3n create-version.description=Crea una nueva versi\u00f3n create-version.description.display-label=Descripci\u00f3n de versi\u00f3n create-version.minor-change.display-label=Cambio mayor + diff --git a/config/alfresco/messages/action-config_fr.properties b/config/alfresco/messages/action-config_fr.properties index 972b07f598..86e2ae2982 100755 --- a/config/alfresco/messages/action-config_fr.properties +++ b/config/alfresco/messages/action-config_fr.properties @@ -15,7 +15,7 @@ ac-content-properties.size=Taille # Action conditions no-condition.title=Tous les \u00e9l\u00e9ments -no-condition.description=Cette condition correspondra \u00e0 tout \u00e9l\u00e9ment de contenu ajout\u00e9 \u00e0 l'Espace. Utiliser celle-ci quand vous souhaitez appliquer une action \u00e0 tout ce qui est ajout\u00e9 \u00e0 l'Espace. +no-condition.description=Cette condition correspondra \u00e0 tout \u00e9l\u00e9ment de contenu ajout\u00e9 \u00e0 l'espace. Utiliser celle-ci quand vous souhaitez appliquer une action \u00e0 tout ce qui est ajout\u00e9 \u00e0 l'espace. compare-property-value.title=Le nom contient une valeur compare-property-value.description=La r\u00e8gle s'applique \u00e0 tous les \u00e9l\u00e9ments dont les noms pr\u00e9sentent une valeur sp\u00e9cifique. @@ -28,13 +28,13 @@ in-category.title=Poss\u00e8de une cat\u00e9gorie in-category.description=La r\u00e8gle s'applique \u00e0 tous les \u00e9l\u00e9ments poss\u00e9dant la valeur de cat\u00e9gorie sp\u00e9cifi\u00e9e. in-category.category-aspect.display-label=Aspect de la cat\u00e9gorie in-category.category-value.display-label=Valeur de la cat\u00e9gorie - + is-subtype.title=Contenu du type ou du sous-type is-subtype.description=La r\u00e8gle est appliqu\u00e9e \u00e0 tout \u00e9l\u00e9ment appartenant \u00e0 un type ou un sous-type sp\u00e9cifique is-subtype.type.display-label=Type has-aspect.title=Poss\u00e8de un aspect -has-aspect.description=La r\u00e8gle est appliqu\u00e9e \u00e0 tout \u00e9l\u00e9ment li\u00e9 \u00e0 un certain aspect +has-aspect.description=La r\u00e8gle est appliqu\u00e9e \u00e0 tout \u00e9l\u00e9ment li\u00e9 \u00e0 un certain aspect. has-aspect.aspect.display-label=Aspect compare-mime-type.title=Contenu du type MIME @@ -46,7 +46,7 @@ composite-condition.title=Condition composite composite-condition.description=Combine plusieurs conditions pour cr\u00e9er une condition plus complexe. compare-date-property.title=Propri\u00e9t\u00e9 avec valeur de date -compare-date-property.description=Compare une propri\u00e9t\u00e9 de type date dans les m\u00e9tadonn\u00e9es, aspects ou types. +compare-date-property.description=Compare une propri\u00e9t\u00e9 de type date dans les m\u00e9tadonn\u00e9es, aspects ou types compare-integer-property.title=Propri\u00e9t\u00e9 avec valeur de nombre compare-integer-property.description=Compare une propri\u00e9t\u00e9 de nombre de la m\u00e9tadonn\u00e9e, de l'aspect ou du type @@ -54,9 +54,9 @@ compare-integer-property.description=Compare une propri\u00e9t\u00e9 de nombre d compare-text-property.title=Propri\u00e9t\u00e9 avec valeur de texte compare-text-property.description=Compare une propri\u00e9t\u00e9 de type texte dans les m\u00e9tadonn\u00e9es, aspects ou types -has-tag.title=Poss\u00e8de une \u00e9tiquette -has-tag.description=Poss\u00e8de une \u00e9tiquette appliqu\u00e9e sur un n\u009cud. -has-tag.tag.display-label=\u00c9tiquette +has-tag.title=Poss\u00e8de un tag +has-tag.description=Poss\u00e8de un tag appliqu\u00e9 sur un n\u0153ud. +has-tag.tag.display-label=Tag # Actions @@ -68,8 +68,8 @@ remove-features.title=Supprimer un aspect remove-features.description=Cela va retirer l'aspect de l'\u00e9l\u00e9ment correspondant. remove-features.aspect-name.display-label=Aspect -simple-workflow.title=Ajouter un flux de travail simple -simple-workflow.description=Ceci ajoute un workflow basique \u00e0 l'\u00e9l\u00e9ment correspondant. Ce qui permet \u00e0 l'\u00e9l\u00e9ment d'\u00eatre d\u00e9plac\u00e9 vers un Dossier diff\u00e9rent avant de passer \u00e0 l'\u00e9tape suivante dans le workflow. Il est aussi possible de pr\u00e9ciser un Espace o\u00f9 il sera d\u00e9plac\u00e9 en cas de rejet. +simple-workflow.title=Associer un workflow simple +simple-workflow.description=Ceci ajoute un workflow basique \u00e0 l'\u00e9l\u00e9ment correspondant. Ce qui permet \u00e0 l'\u00e9l\u00e9ment d'\u00eatre d\u00e9plac\u00e9 vers un Dossier diff\u00e9rent avant de passer \u00e0 l'\u00e9tape suivante dans le workflow. Il est aussi possible de pr\u00e9ciser un espace o\u00f9 il sera d\u00e9plac\u00e9 en cas de rejet. simple-workflow.approve-step.display-label=Approuver l'\u00e9tape simple-workflow.approve-folder.display-label=Approuver le dossier d'\u00e9tapes simple-workflow.approve-move.display-label=Approuver le d\u00e9placement @@ -88,15 +88,15 @@ transform.mime-type.display-label=Type MIME transform.destination-folder.display-label=Dossier de destination transform.assoc-type.display-label=Type d'association transform.assoc-name.display-label=Nom de l'association -transform.overwrite-copy.display-label=\u00c9craser la copie +transform.overwrite-copy.display-label=Ecraser la copie transform-image.title=Transformer et copier l'image -transform-image.description=Cette action convertie une image et la copie vers un espace sp\u00e9cifique. +transform-image.description=Cette action convertit une image et la copie vers un espace sp\u00e9cifique transform-image.mime-type.display-label=Type MIME transform-image.destination-folder.display-label=Dossier de destination transform-image.assoc-type.display-label=Type d'association transform-image.assoc-name.display-label=Nom de l'association -transform-image.overwrite-copy.display-label=\u00c9craser la copie +transform-image.overwrite-copy.display-label=Ecraser la copie transform-image.convert-command.display-label=Commande de conversion copy.title=Copier @@ -105,7 +105,7 @@ copy.destination-folder.display-label=Dossier de destination copy.assoc-type.display-label=Type d'association copy.assoc-name.display-label=Nom de l'association copy.deep-copy.display-label=Copie compl\u00e8te -copy.overwrite-copy.display-label=\u00c9craser la copie +copy.overwrite-copy.display-label=Ecraser la copie move.title=D\u00e9placer move.description=Cette action d\u00e9place l'\u00e9l\u00e9ment vers une autre destination. @@ -121,6 +121,7 @@ mail.subject.display-label=Sujet mail.text.display-label=Corps mail.from.display-label=De mail.template.display-label=Mod\u00e8le d'e-mail +mail.template_model.display-label=Param\u00e8tres suppl\u00e9mentaires pour le mod\u00e8le d'e-mail check-in.title=Lib\u00e9rer check-in.description=Cette action enregistre l'\u00e9l\u00e9ment. @@ -139,12 +140,12 @@ set-property-value.property.display-label=Propri\u00e9t\u00e9 set-property-value.value.display-label=Valeur import.title=Importer -import.description=Importer un paquetage de contenu dans l'entrep\u00f4t. +import.description=Importer un package de contenu Alfresco dans l'entrep\u00f4t. import.encoding.display-label=Encodage import.destination.display-label=Destination extract-metadata.title=Extraire des champs de m\u00e9tadonn\u00e9es communs -extract-metadata.description=Importe les champs metadata titre, auteur et description des types de contenus classiques. +extract-metadata.description=Importe les champs de m\u00e9tadonn\u00e9es titre, auteur et description des types de contenus classiques. specialise-type.title=Sp\u00e9cialiser le type specialise-type.description=Sp\u00e9cialisation de l'\u00e9l\u00e9ment concern\u00e9 sur un type sp\u00e9cifi\u00e9. @@ -152,10 +153,10 @@ specialise-type.type-name.display-label=Type export.title=Exporter l'espace export.description=Exporte un espace et, facultativement, ses enfants dans un progiciel d'exportation Alfresco. -export.package.description=Paquetage de contenu Alfresco pour l''Espace ''{0}''. -export.root.package.description=Paquetage de contenu Alfresco pour l'Entrep\u00f4t complet. -export.store.package.description=Export de l''espace ''{0}'' de l''Entrep\u00f4t Alfresco. -export.generic.package.description=Export de l'Entrep\u00f4t Alfresco. +export.package.description=Package de contenu Alfresco pour l''espace ''{0}''. +export.root.package.description=Package de contenu Alfresco pour l'entrep\u00f4t complet. +export.store.package.description=Export du magasin ''{0}'' de l''entrep\u00f4t Alfresco. +export.generic.package.description=Export de l'entrep\u00f4t Alfresco. export.package.error=Fichier temporaire d'export non trouv\u00e9 script.title=Ex\u00e9cuter le script @@ -176,36 +177,36 @@ start-workflow.startTaskTransition.display-label=Transition # WCM Actions -simple-avm-submit.title=Publication Directe Simple -simple-avm-submit.description=Transfert tout nouveau noeud de l'\u00e9l\u00e9ment dans l'environnement de recette correspondant. +simple-avm-submit.title=Publication directe simple +simple-avm-submit.description=Transf\u00e8re tout nouveau noeud de l'\u00e9l\u00e9ment dans l'environnement de recette correspondant. simple-avm-promote.title=Simple transfert vers un bac \u00e0 sable -simple-avm-promote.description=Transfert tout nouveau noeud de l'\u00e9l\u00e9ment dans le bac \u00e0 sable s\u00e9lectionn\u00e9. -simple-avm-promote.target-store.display-label=Le nom de l'entrep\u00f4t AVM cible. +simple-avm-promote.description=Transf\u00e8re tout nouveau noeud de l'\u00e9l\u00e9ment dans le bac \u00e0 sable s\u00e9lectionn\u00e9. +simple-avm-promote.target-store.display-label=Nom du magasin AVM cible. -avm-revert-store.title=Remettre un noeud simple dans son \u00e9tat initial dans un entrep\u00f4t -avm-revert-store.description=Remet tous les noeuds dont celui ci-dessous dans sa version initiale. -avm-revert-store.version.display-label=La version vers laquelle revenir. +avm-revert-store.title=R\u00e9tablir un noeud simple dans un magasin. +avm-revert-store.description=R\u00e9tablit une version pr\u00e9c\u00e9dente du noeud indiqu\u00e9 et des noeuds inf\u00e9rieurs. +avm-revert-store.version.display-label=Version \u00e0 r\u00e9tablir. -avm-revert-list.title=Remettre une liste de noeuds dans un entrep\u00f4t dans leur \u00e9tat initial -avm-revert-list.description=Remet tous les noeuds inclus dans la liste dans leur \u00e9tat initial. -avm-revert-list.version.display-label=La version vers laquelle revenir. -avm-revert-list.node-list.display-label=La cha\u00eene encod\u00e9e de la liste des noeuds \u00e0 remettre dans leur \u00e9tat initial. -avm-revert-list.flatten.display-label=S'il faut finaliser un environnement de recette apr\u00e8s le retour en \u00e9tat initial. -avm-revert-list.store.display-label=Le nom de l'entrep\u00f4t remis en \u00e9tat initial, uniquement n\u00e9cessaire pour la finalisation. -avm-revert-list.staging.display-label=Le nom de l'entrep\u00f4t de recette pour la finalisation. -avm-revert-list.flatten-path.display-label=Le chemin relatif de l'entrep\u00f4t qui doit \u00eatre finalis\u00e9. +avm-revert-list.title=R\u00e9tablir une liste de noeuds dans un magasin. +avm-revert-list.description=R\u00e9tablit tous les noeuds de la liste. +avm-revert-list.version.display-label=Version \u00e0 r\u00e9tablir. +avm-revert-list.node-list.display-label=Cha\u00eene encod\u00e9e de la liste des noeuds \u00e0 r\u00e9tablir. +avm-revert-list.flatten.display-label=S'il faut finaliser un environnement de recette apr\u00e8s le r\u00e9tablissement. +avm-revert-list.store.display-label=Nom du magasin r\u00e9tabli, uniquement n\u00e9cessaire pour la finalisation. +avm-revert-list.staging.display-label=Nom du magasin de recette pour la finalisation. +avm-revert-list.flatten-path.display-label=Chemin relatif du magasin qui doit \u00eatre finalis\u00e9. -avm-revert-to-version.title=Remettre un noeud dans une version particuli\u00e8re -avm-revert-to-version.description=Remet un noeud dans une version particuli\u00e8re de ce noeud. -avm-revert-to-version.to-revert.display-label=Le Descripteur de Noeud AVM de la version vers laquelle revenir. +avm-revert-to-version.title=R\u00e9tablir une version particuli\u00e8re d'un noeud. +avm-revert-to-version.description=R\u00e9tablit une version donn\u00e9e d'un noeud. +avm-revert-to-version.to-revert.display-label=Descripteur de noeud AVM de la version \u00e0 r\u00e9tablir. -avm-undo-list.title=Construire une liste de Noeuds dans un entrep\u00f4t, transparant pour l'environnement de recette. +avm-undo-list.title=Rendre une liste de noeuds dans un magasin transparente pour la recette. avm-undo-list.description=Retour arri\u00e8re dans le bac \u00e0 sable utilisateur. -avm-undo-list.node-list.display-label=La cha\u00eene encod\u00e9e de la liste des noeuds \u00e0 remettre dans leur \u00e9tat initial. +avm-undo-list.node-list.display-label=Cha\u00eene encod\u00e9e de la liste des noeuds \u00e0 r\u00e9tablir. -avm-deploy-website.title=D\u00e9ploie un site vers un serveur distant -avm-deploy-website.description=Lance le d\u00e9ploiement d'un site web vers un serveur distant. +avm-deploy-website.title=D\u00e9ploie un site vers un serveur distant. +avm-deploy-website.description=Lance le d\u00e9ploiement d'un site Web vers un serveur distant. avm-deploy-website.webproject.display-label=NodeRef du projet Web \u00e0 partir duquel le d\u00e9ploiement est effectu\u00e9. avm-deploy-website.server.display-label=NodeRef du serveur de d\u00e9ploiement vers lequel le d\u00e9ploiement est effectu\u00e9. avm-deploy-website.attempt.display-label=NodeRef de la tentative de d\u00e9ploiement dont ce dernier fait partie. @@ -213,14 +214,15 @@ avm-deploy-website.callback.display-label=Objet listener DeploymentCallback. avm-deploy-website.delay.display-label=D\u00e9lai facultatif \u00e0 appliquer au d\u00e9but d'un d\u00e9ploiement. start-avm-workflow.title=D\u00e9marrer un workflow WCM -start-avm-workflow.description=D\u00e9marre un workflow n\u00e9cessitant un paquetage de workflow AVM. +start-avm-workflow.description=D\u00e9marre un workflow n\u00e9cessitant un paquetage de workflow AVM start-avm-workflow.store-name.display-label=Nom de l'entrep\u00f4t de d\u00e9marrage de la t\u00e2che -start-avm-workflow.workflow-name.display-label=Le nom du workflow WCM \u00e0 ex\u00e9cuter. +start-avm-workflow.workflow-name.display-label=Nom du workflow WCM \u00e0 ex\u00e9cuter. -copy-to-web-project.title=Copier l'\u00e9l\u00e9ment vers un dossier d'un projet web -copy-to-web-project.description=Cette action copie l'\u00e9l\u00e9ment vers un dossier d'un projet web. +copy-to-web-project.title=Copier l'\u00e9l\u00e9ment vers un dossier d'un projet Web +copy-to-web-project.description=Cette action copie l'\u00e9l\u00e9ment vers un dossier d'un projet Web. create-version.title=Cr\u00e9e une version create-version.description=Cr\u00e9e une version create-version.description.display-label=Description de la version create-version.minor-change.display-label=Modification majeure + diff --git a/config/alfresco/messages/action-config_it.properties b/config/alfresco/messages/action-config_it.properties index f549af82a1..3d3c09b4a4 100755 --- a/config/alfresco/messages/action-config_it.properties +++ b/config/alfresco/messages/action-config_it.properties @@ -28,9 +28,9 @@ in-category.title=Ha la categoria in-category.description=La regola viene applicata a tutti gli elementi con il valore di categoria specificato. in-category.category-aspect.display-label=Aspetto della categoria in-category.category-value.display-label=Valore della categoria - + is-subtype.title=Contenuto di tipo o sottotipo -is-subtype.description=La regola viene applicata a tutti gli elementi che sono del tipo specificato o dei relativi sottotipi. +is-subtype.description=La regola viene applicata a tutti gli elementi che sono del tipo specificato o dei relativi sottotipi is-subtype.type.display-label=Tipo has-aspect.title=Ha l'aspetto @@ -46,13 +46,13 @@ composite-condition.title=Condizione composita composite-condition.description=Combina pi\u00f9 condizioni in modo da creare una condizione pi\u00f9 complessa. compare-date-property.title=Propriet\u00e0 con valore di data -compare-date-property.description=Confronta una propriet\u00e0 di data dei metadati, dell'aspetto o del tipo. +compare-date-property.description=Confronta una propriet\u00e0 di data dei metadati, dell'aspetto o del tipo compare-integer-property.title=Propriet\u00e0 con valore numerico -compare-integer-property.description=Confronta una propriet\u00e0 numerica dei metadati, dell'aspetto o del tipo. +compare-integer-property.description=Confronta una propriet\u00e0 numerica dei metadati, dell'aspetto o del tipo compare-text-property.title=Propriet\u00e0 con valore di testo -compare-text-property.description=Confronta una propriet\u00e0 di testo dei metadati, dell'aspetto o del tipo. +compare-text-property.description=Confronta una propriet\u00e0 di testo dei metadati, dell'aspetto o del tipo has-tag.title=Ha il tag has-tag.description=Ha un nodo a cui \u00e8 applicato un tag. @@ -83,7 +83,7 @@ link-category.category-aspect.display-label=Aspetto della categoria link-category.category-value.display-label=Valore della categoria transform.title=Trasforma e copia contenuto -transform.description=Trasforma il contenuto corrispondente e copia il risultato in uno spazio specifico. +transform.description=Trasforma il contenuto corrispondente e copia il risultato in uno spazio specifico transform.mime-type.display-label=Mimetype transform.destination-folder.display-label=Cartella di destinazione transform.assoc-type.display-label=Tipo di associazione @@ -91,7 +91,7 @@ transform.assoc-name.display-label=Nome di associazione transform.overwrite-copy.display-label=Sovrascrivi copia transform-image.title=Trasforma e copia immagine -transform-image.description=Trasforma l'immagine corrispondente e copia il risultato in uno spazio specifico. +transform-image.description=Trasforma l'immagine corrispondente e copia il risultato in uno spazio specifico transform-image.mime-type.display-label=Mimetype transform-image.destination-folder.display-label=Cartella di destinazione transform-image.assoc-type.display-label=Tipo di associazione @@ -121,6 +121,7 @@ mail.subject.display-label=Oggetto mail.text.display-label=Corpo mail.from.display-label=Da mail.template.display-label=Modello di e-mail +mail.template_model.display-label=Parametri aggiuntivi per il modello di e-mail check-in.title=Check In check-in.description=Esegue il Check In del contenuto corrispondente. @@ -213,7 +214,7 @@ avm-deploy-website.callback.display-label=Oggetto listener DeploymentCallback. avm-deploy-website.delay.display-label=Ritardo opzionale da applicare all'inizio di un dispiegamento. start-avm-workflow.title=Avvia un workflow WCM -start-avm-workflow.description=Avvia un workflow in attesa di un pacchetto di workflow AVM. +start-avm-workflow.description=Avvia un workflow in attesa di un pacchetto di workflow AVM start-avm-workflow.store-name.display-label=Nome del deposito per il compito di inizio start-avm-workflow.workflow-name.display-label=Nome del workflow WCM da richiamare. @@ -224,3 +225,4 @@ create-version.title=Crea nuova versione create-version.description=Crea una nuova versione create-version.description.display-label=Descrizione della versione create-version.minor-change.display-label=Cambiamento maggiore + diff --git a/config/alfresco/messages/action-config_ja.properties b/config/alfresco/messages/action-config_ja.properties new file mode 100755 index 0000000000..b2023bd159 --- /dev/null +++ b/config/alfresco/messages/action-config_ja.properties @@ -0,0 +1,228 @@ +# Action parameter constraints +ac-compare-operations.equals=\u7b49\u3057\u3044 +ac-compare-operations.contains=\u542b\u3080 +ac-compare-operations.begins=\u3067\u59cb\u307e\u308b +ac-compare-operations.ends=\u3067\u7d42\u308f\u308b +ac-compare-operations.greater_than=\u3088\u308a\u5927\u304d\u3044 +ac-compare-operations.greater_than_equal=\u4ee5\u4e0a +ac-compare-operations.less_than=\u3088\u308a\u5c0f\u3055\u3044 +ac-compare-operations.less_than_equal=\u4ee5\u4e0b + +ac-content-properties.mime_type=MIME\u30bf\u30a4\u30d7 +ac-content-properties.encoding=\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0 +ac-content-properties.size=\u30b5\u30a4\u30ba + +# Action conditions + +no-condition.title=\u5168\u3066\u306e\u30a2\u30a4\u30c6\u30e0 +no-condition.description=\u3053\u306e\u6761\u4ef6\u306f\u3001\u30b9\u30da\u30fc\u30b9\u306b\u8ffd\u52a0\u3055\u308c\u305f\u5168\u3066\u306e\u30a2\u30a4\u30c6\u30e0\u306b\u30de\u30c3\u30c1\u3057\u307e\u3059\u3002 \u30a2\u30af\u30b7\u30e7\u30f3\u304c\u30b9\u30da\u30fc\u30b9\u306b\u8ffd\u52a0\u3055\u308c\u305f\u3068\u304d\u306b\u3001\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u5168\u3066\u306b\u9069\u7528\u3059\u308b\u5834\u5408\u306f\u3053\u308c\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002 + +compare-property-value.title=\u540d\u524d\u306b\u5024\u3092\u542b\u3080 +compare-property-value.description=\u30eb\u30fc\u30eb\u306f\u3001\u540d\u524d\u306b\u7279\u5b9a\u306e\u5024\u304c\u542b\u307e\u308c\u3066\u3044\u308b\u5168\u3066\u306e\u30a2\u30a4\u30c6\u30e0\u306b\u9069\u7528\u3055\u308c\u307e\u3059\u3002 +compare-property-value.property.display-label=\u30d7\u30ed\u30d1\u30c6\u30a3 +compare-property-value.content-property.display-label=\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30d7\u30ed\u30d1\u30c6\u30a3 +compare-property-value.value.display-label=\u5024 +compare-property-value.operation.display-label=\u6bd4\u8f03\u6f14\u7b97 + +in-category.title=\u30ab\u30c6\u30b4\u30ea\u304c\u3042\u308b +in-category.description=\u30eb\u30fc\u30eb\u306f\u3001\u6307\u5b9a\u3055\u308c\u305f\u30ab\u30c6\u30b4\u30ea\u5024\u3092\u6301\u3064\u5168\u3066\u306e\u30a2\u30a4\u30c6\u30e0\u306b\u9069\u7528\u3055\u308c\u307e\u3059\u3002 +in-category.category-aspect.display-label=\u30ab\u30c6\u30b4\u30ea\u306e\u30a2\u30b9\u30da\u30af\u30c8 +in-category.category-value.display-label=\u30ab\u30c6\u30b4\u30ea\u306e\u5024 + +is-subtype.title=\u30bf\u30a4\u30d7\u307e\u305f\u306f\u30b5\u30d6\u30bf\u30a4\u30d7\u306e\u30b3\u30f3\u30c6\u30f3\u30c4 +is-subtype.description=\u30eb\u30fc\u30eb\u306f\u3001\u6307\u5b9a\u3055\u308c\u305f\u30bf\u30a4\u30d7\u307e\u305f\u306f\u305d\u306e\u30b5\u30d6\u30bf\u30a4\u30d7\u3067\u3042\u308b\u5168\u3066\u306e\u30a2\u30a4\u30c6\u30e0\u306b\u9069\u7528\u3055\u308c\u307e\u3059\u3002 +is-subtype.type.display-label=\u30bf\u30a4\u30d7 + +has-aspect.title=\u30a2\u30b9\u30da\u30af\u30c8\u3092\u6301\u3064 +has-aspect.description=\u30eb\u30fc\u30eb\u306f\u3001\u6307\u5b9a\u3055\u308c\u305f\u30a2\u30b9\u30da\u30af\u30c8\u304c\u9069\u7528\u3055\u308c\u3066\u3044\u308b\u5168\u3066\u306e\u30a2\u30a4\u30c6\u30e0\u306b\u9069\u7528\u3055\u308c\u307e\u3059\u3002 +has-aspect.aspect.display-label=\u30a2\u30b9\u30da\u30af\u30c8 + +compare-mime-type.title=MIME\u30bf\u30a4\u30d7\u306e\u30b3\u30f3\u30c6\u30f3\u30c4 +compare-mime-type.description=\u30eb\u30fc\u30eb\u306f\u3001\u6307\u5b9a\u3055\u308c\u305fMIME\u30bf\u30a4\u30d7\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u3092\u6301\u3064\u5168\u3066\u306e\u30a2\u30a4\u30c6\u30e0\u306b\u9069\u7528\u3055\u308c\u307e\u3059\u3002 +compare-mime-type.property.display-label=\u30d7\u30ed\u30d1\u30c6\u30a3 +compare-mime-type.value.display-label=MIME\u30bf\u30a4\u30d7 + +composite-condition.title=\u5408\u6210\u6761\u4ef6 +composite-condition.description=\u8907\u6570\u306e\u6761\u4ef6\u3092\u7d44\u307f\u5408\u308f\u305b\u3066\u3088\u308a\u8907\u96d1\u306a1\u3064\u306e\u6761\u4ef6\u3092\u4f5c\u6210\u3057\u307e\u3059\u3002 + +compare-date-property.title=\u65e5\u4ed8\u5024\u3092\u6301\u3064\u30d7\u30ed\u30d1\u30c6\u30a3 +compare-date-property.description=\u30e1\u30bf\u30c7\u30fc\u30bf\u3001\u30a2\u30b9\u30da\u30af\u30c8\u3001\u307e\u305f\u306f\u30bf\u30a4\u30d7\u306e\u65e5\u4ed8\u30d7\u30ed\u30d1\u30c6\u30a3\u3092\u6bd4\u8f03\u3057\u307e\u3059\u3002 + +compare-integer-property.title=\u6570\u5024\u3092\u6301\u3064\u30d7\u30ed\u30d1\u30c6\u30a3 +compare-integer-property.description=\u30e1\u30bf\u30c7\u30fc\u30bf\u3001\u30a2\u30b9\u30da\u30af\u30c8\u3001\u307e\u305f\u306f\u30bf\u30a4\u30d7\u306e\u6570\u5b57\u30d7\u30ed\u30d1\u30c6\u30a3\u3092\u6bd4\u8f03\u3057\u307e\u3059\u3002 + +compare-text-property.title=\u30c6\u30ad\u30b9\u30c8\u5024\u3092\u6301\u3064\u30d7\u30ed\u30d1\u30c6\u30a3 +compare-text-property.description=\u30e1\u30bf\u30c7\u30fc\u30bf\u3001\u30a2\u30b9\u30da\u30af\u30c8\u3001\u307e\u305f\u306f\u30bf\u30a4\u30d7\u306e\u30c6\u30ad\u30b9\u30c8\u30fb\u30d7\u30ed\u30d1\u30c6\u30a3\u3092\u6bd4\u8f03\u3057\u307e\u3059\u3002 + +has-tag.title=\u30bf\u30b0\u304c\u3042\u308b +has-tag.description=\u30bf\u30b0\u304c\u30ce\u30fc\u30c9\u306b\u9069\u7528\u3055\u308c\u3066\u3044\u307e\u3059\u3002 +has-tag.tag.display-label=\u30bf\u30b0 + +# Actions + +add-features.title=\u30a2\u30b9\u30da\u30af\u30c8\u3092\u8ffd\u52a0\u3059\u308b +add-features.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30a2\u30b9\u30da\u30af\u30c8\u304c\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u306b\u8ffd\u52a0\u3055\u308c\u307e\u3059\u3002 +add-features.aspect-name.display-label=\u30a2\u30b9\u30da\u30af\u30c8 + +remove-features.title=\u30a2\u30b9\u30da\u30af\u30c8\u3092\u53d6\u9664\u304f +remove-features.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30a2\u30b9\u30da\u30af\u30c8\u304c\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u304b\u3089\u53d6\u9664\u304b\u308c\u307e\u3059\u3002 +remove-features.aspect-name.display-label=\u30a2\u30b9\u30da\u30af\u30c8 + +simple-workflow.title=\u7c21\u6613\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u3092\u8ffd\u52a0\u3059\u308b +simple-workflow.description=\u3053\u308c\u306b\u3088\u308a\u3001\u7c21\u6613\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u304c\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u306b\u8ffd\u52a0\u3055\u308c\u307e\u3059\u3002 \u3053\u308c\u306b\u3088\u308a\u3001\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u306e\u6b21\u306e\u30b9\u30c6\u30c3\u30d7\u306b\u3064\u3044\u3066\u3001\u7570\u306a\u308b\u30b9\u30da\u30fc\u30b9\u306b\u30a2\u30a4\u30c6\u30e0\u3092\u79fb\u52d5\u3067\u304d\u308b\u3088\u3046\u306b\u306a\u308a\u307e\u3059\u3002 \u307e\u305f\u3001\u5374\u4e0b\u30b9\u30c6\u30c3\u30d7\u304c\u5fc5\u8981\u306a\u5834\u5408\u306f\u3001\u79fb\u52d5\u5148\u306e\u30b9\u30da\u30fc\u30b9\u3092\u63d0\u4f9b\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002 +simple-workflow.approve-step.display-label=\u627f\u8a8d\u30b9\u30c6\u30c3\u30d7 +simple-workflow.approve-folder.display-label=\u627f\u8a8d\u30b9\u30c6\u30c3\u30d7\u30fb\u30d5\u30a9\u30eb\u30c0 +simple-workflow.approve-move.display-label=\u79fb\u52d5\u3092\u627f\u8a8d\u3059\u308b +simple-workflow.reject-step.display-label=\u5374\u4e0b\u30b9\u30c6\u30c3\u30d7 +simple-workflow.reject-folder.display-label=\u5374\u4e0b\u30b9\u30c6\u30c3\u30d7\u30fb\u30d5\u30a9\u30eb\u30c0 +simple-workflow.reject-move.display-label=\u79fb\u52d5\u3092\u5374\u4e0b\u3059\u308b + +link-category.title=\u30ab\u30c6\u30b4\u30ea\u306b\u30ea\u30f3\u30af\u3059\u308b +link-category.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u306b\u30ab\u30c6\u30b4\u30ea\u304c\u9069\u7528\u3055\u308c\u307e\u3059\u3002 +link-category.category-aspect.display-label=\u30ab\u30c6\u30b4\u30ea\u306e\u30a2\u30b9\u30da\u30af\u30c8 +link-category.category-value.display-label=\u30ab\u30c6\u30b4\u30ea\u306e\u5024 + +transform.title=\u30b3\u30f3\u30c6\u30f3\u30c4\u3092\u5909\u63db\u304a\u3088\u3073\u30b3\u30d4\u30fc\u3059\u308b +transform.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u5909\u63db\u3055\u308c\u3001\u7279\u5b9a\u306e\u30b9\u30da\u30fc\u30b9\u306b\u7d50\u679c\u304c\u30b3\u30d4\u30fc\u3055\u308c\u307e\u3059\u3002 +transform.mime-type.display-label=MIME\u30bf\u30a4\u30d7 +transform.destination-folder.display-label=\u5b9b\u5148\u30d5\u30a9\u30eb\u30c0 +transform.assoc-type.display-label=\u95a2\u9023\u30bf\u30a4\u30d7 +transform.assoc-name.display-label=\u95a2\u9023\u540d +transform.overwrite-copy.display-label=\u30b3\u30d4\u30fc\u306b\u4e0a\u66f8\u304d\u3059\u308b + +transform-image.title=\u753b\u50cf\u3092\u5909\u63db\u304a\u3088\u3073\u30b3\u30d4\u30fc\u3059\u308b +transform-image.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u753b\u50cf\u304c\u5909\u63db\u3055\u308c\u3001\u7279\u5b9a\u306e\u30b9\u30da\u30fc\u30b9\u306b\u7d50\u679c\u304c\u30b3\u30d4\u30fc\u3055\u308c\u307e\u3059\u3002 +transform-image.mime-type.display-label=MIME\u30bf\u30a4\u30d7 +transform-image.destination-folder.display-label=\u5b9b\u5148\u30d5\u30a9\u30eb\u30c0 +transform-image.assoc-type.display-label=\u95a2\u9023\u30bf\u30a4\u30d7 +transform-image.assoc-name.display-label=\u95a2\u9023\u540d +transform-image.overwrite-copy.display-label=\u30b3\u30d4\u30fc\u306b\u4e0a\u66f8\u304d\u3059\u308b +transform-image.convert-command.display-label=\u5909\u63db\u30b3\u30de\u30f3\u30c9 + +copy.title=\u30b3\u30d4\u30fc +copy.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u304c\u5225\u306e\u30b9\u30da\u30fc\u30b9\u306b\u30b3\u30d4\u30fc\u3055\u308c\u307e\u3059\u3002 +copy.destination-folder.display-label=\u5b9b\u5148\u30d5\u30a9\u30eb\u30c0 +copy.assoc-type.display-label=\u95a2\u9023\u30bf\u30a4\u30d7 +copy.assoc-name.display-label=\u95a2\u9023\u540d +copy.deep-copy.display-label=\u30c7\u30a3\u30fc\u30d7\u30fb\u30b3\u30d4\u30fc +copy.overwrite-copy.display-label=\u30b3\u30d4\u30fc\u306b\u4e0a\u66f8\u304d\u3059\u308b + +move.title=\u79fb\u52d5 +move.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u304c\u5225\u306e\u30b9\u30da\u30fc\u30b9\u306b\u79fb\u52d5\u3055\u308c\u307e\u3059\u3002 +move.destination-folder.display-label=\u5b9b\u5148\u30d5\u30a9\u30eb\u30c0 +move.assoc-type.display-label=\u95a2\u9023\u30bf\u30a4\u30d7 +move.assoc-name.display-label=\u95a2\u9023\u540d + +mail.title=E\u30e1\u30fc\u30eb\u3092\u9001\u4fe1\u3059\u308b +mail.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u30de\u30c3\u30c1\u3059\u308b\u3068\u3001\u3053\u308c\u306b\u3088\u308a\u30e6\u30fc\u30b6\u306e\u30ea\u30b9\u30c8\u306bE\u30e1\u30fc\u30eb\u304c\u9001\u4fe1\u3055\u308c\u307e\u3059\u3002 +mail.to.display-label=\u7d42\u4e86\u65e5 +mail.to_many.display-label=\u7d42\u4e86\u65e5 +mail.subject.display-label=\u4ef6\u540d +mail.text.display-label=\u672c\u6587 +mail.from.display-label=\u958b\u59cb\u65e5 +mail.template.display-label=E\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +mail.template_model.display-label=E\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u306e\u8ffd\u52a0\u306e\u30d1\u30e9\u30e1\u30fc\u30bf + +check-in.title=\u30c1\u30a7\u30c3\u30af\u30a4\u30f3 +check-in.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u30c1\u30a7\u30c3\u30af\u30a4\u30f3\u3055\u308c\u307e\u3059\u3002 +check-in.description.display-label=\u8aac\u660e +check-in.minorChange.display-label=\u5c0f\u3055\u306a\u5909\u66f4 + +check-out.title=\u30c1\u30a7\u30c3\u30af\u30a2\u30a6\u30c8 +check-out.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u30c1\u30a7\u30c3\u30af\u30a2\u30a6\u30c8\u3055\u308c\u307e\u3059\u3002 +check-out.destination-folder.display-label=\u5b9b\u5148\u30d5\u30a9\u30eb\u30c0 +check-out.assoc-type.display-label=\u95a2\u9023\u30bf\u30a4\u30d7 +check-out.assoc-name.display-label=\u95a2\u9023\u540d + +set-property-value.title=\u30d7\u30ed\u30d1\u30c6\u30a3\u5024\u3092\u8a2d\u5b9a\u3059\u308b +set-property-value.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30d7\u30ed\u30d1\u30c6\u30a3\u306e\u5024\u304c\u7279\u5b9a\u306e\u5024\u306b\u8a2d\u5b9a\u3055\u308c\u307e\u3059\u3002 +set-property-value.property.display-label=\u30d7\u30ed\u30d1\u30c6\u30a3 +set-property-value.value.display-label=\u5024 + +import.title=\u30a4\u30f3\u30dd\u30fc\u30c8 +import.description=Alfresco\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u30fb\u30d1\u30c3\u30b1\u30fc\u30b8\u304c\u30ea\u30dd\u30b8\u30c8\u30ea\u306b\u30a4\u30f3\u30dd\u30fc\u30c8\u3055\u308c\u307e\u3059\u3002 +import.encoding.display-label=\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0 +import.destination.display-label=\u5b9b\u5148 + +extract-metadata.title=\u5171\u901a\u306e\u30e1\u30bf\u30c7\u30fc\u30bf\u30fb\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u62bd\u51fa\u3059\u308b +extract-metadata.description=\u5171\u901a\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u30fb\u30bf\u30a4\u30d7\u304b\u3089\u30bf\u30a4\u30c8\u30eb\u3001\u4f5c\u6210\u8005\u3001\u304a\u3088\u3073\u8aac\u660e\u306e\u30e1\u30bf\u30c7\u30fc\u30bf\u30fb\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u30a4\u30f3\u30dd\u30fc\u30c8\u3057\u307e\u3059\u3002 + +specialise-type.title=\u30bf\u30a4\u30d7\u3092\u7279\u6b8a\u5316\u3059\u308b +specialise-type.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u304c\u7279\u5b9a\u306e\u30bf\u30a4\u30d7\u3078\u3068\u7279\u6b8a\u5316\u3055\u308c\u307e\u3059\u3002 +specialise-type.type-name.display-label=\u30bf\u30a4\u30d7 + +export.title=\u30b9\u30da\u30fc\u30b9\u3092\u30a8\u30af\u30b9\u30dd\u30fc\u30c8\u3059\u308b +export.description=\u30b9\u30da\u30fc\u30b9\u3068\u305d\u306e\u5b50\uff08\u30aa\u30d7\u30b7\u30e7\u30f3\uff09\u304cAlfresco\u30a8\u30af\u30b9\u30dd\u30fc\u30c8\u30fb\u30d1\u30c3\u30b1\u30fc\u30b8\u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8\u3055\u308c\u307e\u3059\u3002 +export.package.description=\u30b9\u30da\u30fc\u30b9''{0}''\u306eAlfresco\u30b3\u30f3\u30c6\u30f3\u30c4\u30fb\u30d1\u30c3\u30b1\u30fc\u30b8 +export.root.package.description=\u30ea\u30dd\u30b8\u30c8\u30ea\u5168\u4f53\u306eAlfresco\u30b3\u30f3\u30c6\u30f3\u30c4\u30fb\u30d1\u30c3\u30b1\u30fc\u30b8 +export.store.package.description=\u30b9\u30c8\u30a2''{0}''\u306eAlfresco\u30ea\u30dd\u30b8\u30c8\u30ea\u30fb\u30a8\u30af\u30b9\u30dd\u30fc\u30c8 +export.generic.package.description=Alfresco\u30ea\u30dd\u30b8\u30c8\u30ea\u30fb\u30a8\u30af\u30b9\u30dd\u30fc\u30c8 +export.package.error=\u30a8\u30af\u30b9\u30dd\u30fc\u30c8\u7528\u306e\u4e00\u6642\u30d5\u30a1\u30a4\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f + +script.title=\u30b9\u30af\u30ea\u30d7\u30c8\u3092\u5b9f\u884c\u3059\u308b +script.description=JavaScript\u30d5\u30a1\u30a4\u30eb\u3092\u5b9f\u884c\u3057\u3001\u65b0\u3057\u3044\u30d5\u30a1\u30a4\u30eb\u3084\u30d5\u30a9\u30eb\u30c0\u3092\u4f5c\u6210\u3059\u308b\u306a\u3069\u306e\u30bf\u30b9\u30af\u3092\u5b9f\u884c\u3057\u307e\u3059\u3002 +script.script-ref.display-label=\u30b9\u30af\u30ea\u30d7\u30c8 + +counter.title=\u30ab\u30a6\u30f3\u30bf\u3092\u5897\u5206\u3059\u308b +counter.counter=\u30a2\u30a4\u30c6\u30e0\u306e\u30ab\u30a6\u30f3\u30bf\u30fb\u30d7\u30ed\u30d1\u30c6\u30a3\u304c\u5897\u5206\u3055\u308c\u307e\u3059\u3002 + +execute-all-rules.title=\u5168\u3066\u306e\u30eb\u30fc\u30eb\u3092\u5b9f\u884c\u3059\u308b +execute-all-rules.description=\u5b50\u30a2\u30a4\u30c6\u30e0\u306b\u5bfe\u3057\u3066\u5168\u3066\u306e\u30eb\u30fc\u30eb\u3092\u5b9f\u884c\u3057\u307e\u3059\u3002 + +start-workflow.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u306e\u958b\u59cb +start-workflow.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u306e\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u304c\u958b\u59cb\u3055\u308c\u307e\u3059\u3002 +start-workflow.workflowName.display-label=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u540d +start-workflow.endStartTask.display-label=\u30bf\u30b9\u30af +start-workflow.startTaskTransition.display-label=\u79fb\u884c + +# WCM Actions + +simple-avm-submit.title=\u7c21\u6613\u76f4\u63a5\u9001\u4fe1 +simple-avm-submit.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u306e\u65b0\u3057\u3044\u30ce\u30fc\u30c9\u304c\u5bfe\u5fdc\u3059\u308b\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u306b\u9001\u4fe1\u3055\u308c\u307e\u3059\u3002 + +simple-avm-promote.title=\u7c21\u6613\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u30fb\u30d7\u30ed\u30e2\u30fc\u30b7\u30e7\u30f3 +simple-avm-promote.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u306e\u65b0\u3057\u3044\u30ce\u30fc\u30c9\u304c\u3001\u6307\u5b9a\u3055\u308c\u305f\u30bf\u30fc\u30b2\u30c3\u30c8\u30fb\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u306b\u6607\u683c\u3055\u308c\u307e\u3059\u3002 +simple-avm-promote.target-store.display-label=\u30bf\u30fc\u30b2\u30c3\u30c8AVM\u30b9\u30c8\u30a2\u306e\u540d\u524d\u3002 + +avm-revert-store.title=\u30b9\u30c8\u30a2\u306e\u30ce\u30fc\u30c9\u30921\u3064\u30ea\u30d0\u30fc\u30c8\u3059\u308b +avm-revert-store.description=\u3053\u308c\u306b\u3088\u308a\u3001\u5f15\u6570\u30ce\u30fc\u30c9\u3068\u305d\u306e\u4e0b\u306e\u30ce\u30fc\u30c9\u3092\u542b\u3080\u5168\u3066\u306e\u30ce\u30fc\u30c9\u304c\u4ee5\u524d\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u306b\u30ea\u30d0\u30fc\u30c8\u3055\u308c\u307e\u3059\u3002 +avm-revert-store.version.display-label=\u30ea\u30d0\u30fc\u30c8\u3059\u308b\u30d0\u30fc\u30b8\u30e7\u30f3 + +avm-revert-list.title=\u30b9\u30c8\u30a2\u306e\u30ce\u30fc\u30c9\u306e\u30ea\u30b9\u30c8\u3092\u30ea\u30d0\u30fc\u30c8\u3059\u308b +avm-revert-list.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30ea\u30b9\u30c8\u306b\u542b\u307e\u308c\u3066\u3044\u308b\u5168\u3066\u306e\u30ce\u30fc\u30c9\u304c\u30ea\u30d0\u30fc\u30c8\u3055\u308c\u307e\u3059\u3002 +avm-revert-list.version.display-label=\u30ea\u30d0\u30fc\u30c8\u3059\u308b\u30d0\u30fc\u30b8\u30e7\u30f3 +avm-revert-list.node-list.display-label=\u30ea\u30d0\u30fc\u30c8\u5bfe\u8c61\u30ce\u30fc\u30c9\u306e\u6587\u5b57\u5217\u304c\u30a8\u30f3\u30b3\u30fc\u30c9\u3055\u308c\u305f\u30ea\u30b9\u30c8\u3002 +avm-revert-list.flatten.display-label=\u30ea\u30d0\u30fc\u30c8\u5f8c\u306b\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u30fb\u30b9\u30c8\u30a2\u3078\u3068\u5e73\u5766\u5316\u3059\u308b\u304b +avm-revert-list.store.display-label=\u30ea\u30d0\u30fc\u30c8\u3059\u308b\u30b9\u30c8\u30a2\u306e\u540d\u524d\u3002\u5e73\u5766\u5316\u3059\u308b\u5834\u5408\u306e\u307f\u5fc5\u8981\u3002 +avm-revert-list.staging.display-label=\u5e73\u5766\u5316\u5f8c\u306e\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u30fb\u30b9\u30c8\u30a2\u306e\u540d\u524d\u3002 +avm-revert-list.flatten-path.display-label=\u5e73\u5766\u5316\u3059\u308b\u3079\u304d\u30b9\u30c8\u30a2\u306e\u76f8\u5bfe\u30d1\u30b9\u3002 + +avm-revert-to-version.title=\u30ce\u30fc\u30c9\u3092\u7279\u5b9a\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u306b\u30ea\u30d0\u30fc\u30c8\u3059\u308b +avm-revert-to-version.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30ce\u30fc\u30c9\u304c\u305d\u306e\u30ce\u30fc\u30c9\u306e\u7279\u5b9a\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u306b\u30ea\u30d0\u30fc\u30c8\u3055\u308c\u307e\u3059\u3002 +avm-revert-to-version.to-revert.display-label=\u30ea\u30d0\u30fc\u30c8\u5f8c\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u306eAVM\u30ce\u30fc\u30c9\u8a18\u8ff0\u5b50\u3002 + +avm-undo-list.title=\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u306b\u900f\u904e\u7684\u306a\u30b9\u30c8\u30a2\u306e\u30ce\u30fc\u30c9\u306e\u30ea\u30b9\u30c8\u3092\u4f5c\u308b +avm-undo-list.description=\u3053\u308c\u306f\u3001\u30e6\u30fc\u30b6\u306e\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u306e\u8aa4\u308a\u89e3\u6d88\u6a5f\u80fd\u3068\u3057\u3066\u6a5f\u80fd\u3057\u307e\u3059\u3002 +avm-undo-list.node-list.display-label=\u30ea\u30d0\u30fc\u30c8\u5bfe\u8c61\u30ce\u30fc\u30c9\u306e\u6587\u5b57\u5217\u304c\u30a8\u30f3\u30b3\u30fc\u30c9\u3055\u308c\u305f\u30ea\u30b9\u30c8\u3002 + +avm-deploy-website.title=Web\u30b5\u30a4\u30c8\u3092\u30ea\u30e2\u30fc\u30c8\u30fb\u30b5\u30fc\u30d0\u306b\u30c7\u30d7\u30ed\u30a4\u3059\u308b +avm-deploy-website.description=\u3053\u308c\u306b\u3088\u308a\u3001Web\u30b5\u30a4\u30c8\u304c\u30ea\u30e2\u30fc\u30c8\u30fb\u30b5\u30fc\u30d0\u306b\u30c7\u30d7\u30ed\u30a4\u3055\u308c\u307e\u3059\u3002 +avm-deploy-website.webproject.display-label=\u30c7\u30d7\u30ed\u30a4\u304c\u767a\u751f\u3057\u3066\u3044\u308bWeb\u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u306enodeRef\u3002 +avm-deploy-website.server.display-label=\u30c7\u30d7\u30ed\u30a4\u5148\u306e\u30c7\u30d7\u30ed\u30a4\u30fb\u30b5\u30fc\u30d0\u306enodeRef\u3002 +avm-deploy-website.attempt.display-label=\u3053\u306e\u30c7\u30d7\u30ed\u30a4\u304c\u5c5e\u3059\u308b\u30c7\u30d7\u30ed\u30a4\u8a66\u884c\u306enodeRef\u3002 +avm-deploy-website.callback.display-label=DeploymentCallback\u30ea\u30b9\u30ca\u30fb\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u3002 +avm-deploy-website.delay.display-label=\u30c7\u30d7\u30ed\u30a4\u306e\u958b\u59cb\u306b\u9069\u7528\u3059\u308b\u30aa\u30d7\u30b7\u30e7\u30f3\u306e\u9045\u5ef6\u3002 + +start-avm-workflow.title=WCM\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u3092\u958b\u59cb\u3059\u308b +start-avm-workflow.description=AVM\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30d1\u30c3\u30b1\u30fc\u30b8\u3092\u671f\u5f85\u3059\u308b\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u3092\u958b\u59cb\u3059\u308b +start-avm-workflow.store-name.display-label=\u958b\u59cb\u30bf\u30b9\u30af\u306e\u30b9\u30c8\u30a2\u540d +start-avm-workflow.workflow-name.display-label=\u547c\u3073\u51fa\u3059WCM\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u306e\u540d\u524d\u3002 + +copy-to-web-project.title=Web\u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u306e\u30d5\u30a9\u30eb\u30c0\u306b\u30a2\u30a4\u30c6\u30e0\u3092\u30b3\u30d4\u30fc\u3059\u308b +copy-to-web-project.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30de\u30c3\u30c1\u3057\u305f\u30a2\u30a4\u30c6\u30e0\u304cWeb\u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u306e\u30d5\u30a9\u30eb\u30c0\u306b\u30b3\u30d4\u30fc\u3055\u308c\u307e\u3059\u3002 + +create-version.title=\u65b0\u3057\u3044\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u4f5c\u6210\u3059\u308b +create-version.description=\u65b0\u3057\u3044\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u4f5c\u6210\u3059\u308b +create-version.description.display-label=\u30d0\u30fc\u30b8\u30e7\u30f3\u306e\u8aac\u660e +create-version.minor-change.display-label=\u5927\u304d\u306a\u5909\u66f4 + diff --git a/config/alfresco/messages/action-service_fr.properties b/config/alfresco/messages/action-service_fr.properties index a5d3c7b526..8e90245ca8 100755 --- a/config/alfresco/messages/action-service_fr.properties +++ b/config/alfresco/messages/action-service_fr.properties @@ -5,5 +5,5 @@ compare_property_value_evaluator.no_content_property=Une propri\u00e9t\u00e9 de numeric_property_value_comparator.invalid_operation=L''op\u00e9ration {0} ne peut \u00eatre appliqu\u00e9e \u00e0 une propri\u00e9t\u00e9 de type num\u00e9rique. text_property_value_comparator.invalid_operation=L''op\u00e9ration {0} ne peut \u00eatre appliqu\u00e9e \u00e0 une propri\u00e9t\u00e9 de type texte. date_property_value_comparator.invalid_operation=L''op\u00e9ration {0} ne peut \u00eatre appliqu\u00e9e \u00e0 une propri\u00e9t\u00e9 de type date. -compare_mime_type_evaluator.not_a_content_type=La propri\u00e9t\u00e9 sp\u00e9cifi\u00e9e n'est pas de type contenu.Le type MIME ne peut donc pas \u00eatre compar\u00e9. -compare_mime_type_evaluator.no_property_definition_found=Aucune d\u00e9finition n'a \u00e9t\u00e9 trouv\u00e9e pour la propri\u00e9t\u00e9 sp\u00e9cifi\u00e9e.Le type MIME ne peut donc pas \u00eatre compar\u00e9. +compare_mime_type_evaluator.not_a_content_type=La propri\u00e9t\u00e9 sp\u00e9cifi\u00e9e n'est pas de type contenu. Le type MIME ne peut donc pas \u00eatre compar\u00e9. +compare_mime_type_evaluator.no_property_definition_found=Aucune d\u00e9finition n'a \u00e9t\u00e9 trouv\u00e9e pour la propri\u00e9t\u00e9 sp\u00e9cifi\u00e9e. Le type MIME ne peut donc pas \u00eatre compar\u00e9. diff --git a/config/alfresco/messages/action-service_ja.properties b/config/alfresco/messages/action-service_ja.properties new file mode 100755 index 0000000000..fd331e8855 --- /dev/null +++ b/config/alfresco/messages/action-service_ja.properties @@ -0,0 +1,9 @@ +# Action service externalised display strings + +compare_property_value_evaluator.invalid_operation=\u30aa\u30da\u30ec\u30fc\u30b7\u30e7\u30f3{0}\u306f\u30bf\u30a4\u30d7{1}\u306e\u30d7\u30ed\u30d1\u30c6\u30a3\u306b\u9069\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +compare_property_value_evaluator.no_content_property=\u30bf\u30a4\u30d7\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30d7\u30ed\u30d1\u30c6\u30a3\u3068\u6bd4\u8f03\u3059\u308b\u5834\u5408\u306f\u3001\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30d7\u30ed\u30d1\u30c6\u30a3\u304c\u7279\u5b9a\u3055\u308c\u3066\u3044\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093\u3002 +numeric_property_value_comparator.invalid_operation=\u30aa\u30da\u30ec\u30fc\u30b7\u30e7\u30f3{0}\u306f\u6570\u5024\u30d7\u30ed\u30d1\u30c6\u30a3\u306b\u9069\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +text_property_value_comparator.invalid_operation=\u30aa\u30da\u30ec\u30fc\u30b7\u30e7\u30f3{0}\u306f\u30c6\u30ad\u30b9\u30c8\u30d7\u30ed\u30d1\u30c6\u30a3\u306b\u9069\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +date_property_value_comparator.invalid_operation=\u30aa\u30da\u30ec\u30fc\u30b7\u30e7\u30f3{0}\u306f\u65e5\u4ed8\u30d7\u30ed\u30d1\u30c6\u30a3\u306b\u9069\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +compare_mime_type_evaluator.not_a_content_type=\u7279\u5b9a\u306e\u30d7\u30ed\u30d1\u30c6\u30a3\u306f\u30b3\u30f3\u30c6\u30f3\u30c4\u30bf\u30a4\u30d7\u3067\u306f\u306a\u3044\u306e\u3067MIME\u30bf\u30a4\u30d7\u3068\u6bd4\u8f03\u3067\u304d\u307e\u305b\u3093\u3002 +compare_mime_type_evaluator.no_property_definition_found=\u7279\u5b9a\u306e\u30d7\u30ed\u30d1\u30c6\u30a3\u306e\u5b9a\u7fa9\u304c\u898b\u3064\u304b\u3089\u306a\u3044\u305f\u3081MIME\u30bf\u30a4\u30d7\u3068\u6bd4\u8f03\u3067\u304d\u307e\u305b\u3093\u3002 diff --git a/config/alfresco/messages/activities-service.properties b/config/alfresco/messages/activities-service.properties new file mode 100644 index 0000000000..05b6082243 --- /dev/null +++ b/config/alfresco/messages/activities-service.properties @@ -0,0 +1,3 @@ +# Activities (Activity Service / Feeds) + +activities.feed.notifier.email.subject=Alfresco {0}: Recent Activities \ No newline at end of file diff --git a/config/alfresco/messages/activities-service_de.properties b/config/alfresco/messages/activities-service_de.properties new file mode 100755 index 0000000000..6a97aaaba4 --- /dev/null +++ b/config/alfresco/messages/activities-service_de.properties @@ -0,0 +1,3 @@ +# Activities (Activity Service / Feeds) + +activities.feed.notifier.email.subject=Alfresco {0}: Letzte Aktivit\u00e4ten \ No newline at end of file diff --git a/config/alfresco/messages/activities-service_es.properties b/config/alfresco/messages/activities-service_es.properties new file mode 100755 index 0000000000..1b54eae832 --- /dev/null +++ b/config/alfresco/messages/activities-service_es.properties @@ -0,0 +1,3 @@ +# Activities (Activity Service / Feeds) + +activities.feed.notifier.email.subject={0} Alfresco: Actividades recientes \ No newline at end of file diff --git a/config/alfresco/messages/activities-service_fr.properties b/config/alfresco/messages/activities-service_fr.properties new file mode 100755 index 0000000000..4e710d8871 --- /dev/null +++ b/config/alfresco/messages/activities-service_fr.properties @@ -0,0 +1,3 @@ +# Activities (Activity Service / Feeds) + +activities.feed.notifier.email.subject=Alfresco {0} : Activit\u00e9s r\u00e9centes \ No newline at end of file diff --git a/config/alfresco/messages/activities-service_it.properties b/config/alfresco/messages/activities-service_it.properties new file mode 100755 index 0000000000..f50032c3b2 --- /dev/null +++ b/config/alfresco/messages/activities-service_it.properties @@ -0,0 +1,3 @@ +# Activities (Activity Service / Feeds) + +activities.feed.notifier.email.subject=Alfresco {0}: Attivit\u00e0 recenti \ No newline at end of file diff --git a/config/alfresco/messages/activities-service_ja.properties b/config/alfresco/messages/activities-service_ja.properties new file mode 100755 index 0000000000..24f9951b53 --- /dev/null +++ b/config/alfresco/messages/activities-service_ja.properties @@ -0,0 +1,3 @@ +# Activities (Activity Service / Feeds) + +activities.feed.notifier.email.subject=Alfresco {0}: \u6700\u8fd1\u306e\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3 \ No newline at end of file diff --git a/config/alfresco/messages/activity-list.properties b/config/alfresco/messages/activity-list.properties new file mode 100644 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/activity-list.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/activity-list_de.properties b/config/alfresco/messages/activity-list_de.properties new file mode 100755 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/activity-list_de.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/activity-list_es.properties b/config/alfresco/messages/activity-list_es.properties new file mode 100755 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/activity-list_es.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/activity-list_fr.properties b/config/alfresco/messages/activity-list_fr.properties new file mode 100755 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/activity-list_fr.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/activity-list_it.properties b/config/alfresco/messages/activity-list_it.properties new file mode 100755 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/activity-list_it.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/activity-list_ja.properties b/config/alfresco/messages/activity-list_ja.properties new file mode 100755 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/activity-list_ja.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/application-model_fr.properties b/config/alfresco/messages/application-model_fr.properties index 653d5c4e95..6937e02e68 100755 --- a/config/alfresco/messages/application-model_fr.properties +++ b/config/alfresco/messages/application-model_fr.properties @@ -18,11 +18,11 @@ app_applicationmodel.aspect.app_inlineeditable.description=Supporte l'\u00e9diti app_applicationmodel.property.app_editInline.title=Editer en Ligne app_applicationmodel.property.app_editInline.description=Editer en Ligne -app_applicationmodel.aspect.app_workflow.title=Flux de travail -app_applicationmodel.aspect.app_workflow.description=Flux de travail +app_applicationmodel.aspect.app_workflow.title=Workflow +app_applicationmodel.aspect.app_workflow.description=Workflow -app_applicationmodel.aspect.app_simpleworkflow.title=Flux de travail -app_applicationmodel.aspect.app_simpleworkflow.description=Flux de travail +app_applicationmodel.aspect.app_simpleworkflow.title=Workflow +app_applicationmodel.aspect.app_simpleworkflow.description=Workflow app_applicationmodel.property.app_approveStep.title=Etape d'Approbation app_applicationmodel.property.app_approveStep.description=Etape d'Approbation app_applicationmodel.property.app_approveFolder.title=Dossier d'Approbation diff --git a/config/alfresco/messages/application-model_ja.properties b/config/alfresco/messages/application-model_ja.properties new file mode 100755 index 0000000000..0d87aa5f4b --- /dev/null +++ b/config/alfresco/messages/application-model_ja.properties @@ -0,0 +1,42 @@ +# Display labels for System Model + +app_applicationmodel.description=Alfresco Application Model + +app_applicationmodel.type.app_glossary.title=\u30c7\u30fc\u30bf\u8f9e\u66f8 +app_applicationmodel.type.app_glossary.description=\u30c7\u30fc\u30bf\u8f9e\u66f8 + +app_applicationmodel.type.app_configurations.title=\u8a2d\u5b9a +app_applicationmodel.type.app_configurations.description=\u8a2d\u5b9a + +app_applicationmodel.aspect.app_uifacets.title=UI\u30d5\u30a1\u30bb\u30c3\u30c8 +app_applicationmodel.aspect.app_uifacets.description=UI\u30d5\u30a1\u30bb\u30c3\u30c8 +app_applicationmodel.property.app_icon.title=\u30a2\u30a4\u30b3\u30f3 +app_applicationmodel.property.app_icon.description=\u30a2\u30a4\u30b3\u30f3 + +app_applicationmodel.aspect.app_inlineeditable.title=\u30a4\u30f3\u30e9\u30a4\u30f3\u7de8\u96c6\u5bfe\u8c61 +app_applicationmodel.aspect.app_inlineeditable.description=\u30a4\u30f3\u30e9\u30a4\u30f3\u7de8\u96c6\u5bfe\u8c61 +app_applicationmodel.property.app_editInline.title=\u30a4\u30f3\u30e9\u30a4\u30f3\u7de8\u96c6 +app_applicationmodel.property.app_editInline.description=\u30a4\u30f3\u30e9\u30a4\u30f3\u7de8\u96c6 + +app_applicationmodel.aspect.app_workflow.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc +app_applicationmodel.aspect.app_workflow.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc + +app_applicationmodel.aspect.app_simpleworkflow.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc +app_applicationmodel.aspect.app_simpleworkflow.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc +app_applicationmodel.property.app_approveStep.title=\u627f\u8a8d\u30b9\u30c6\u30c3\u30d7 +app_applicationmodel.property.app_approveStep.description=\u627f\u8a8d\u30b9\u30c6\u30c3\u30d7 +app_applicationmodel.property.app_approveFolder.title=\u627f\u8a8d\u30d5\u30a9\u30eb\u30c0 +app_applicationmodel.property.app_approveFolder.description=\u627f\u8a8d\u30d5\u30a9\u30eb\u30c0 +app_applicationmodel.property.app_approveMove.title=\u79fb\u52d5\u307e\u305f\u306f\u30b3\u30d4\u30fc +app_applicationmodel.property.app_approveMove.description=\u79fb\u52d5\u307e\u305f\u306f\u30b3\u30d4\u30fc +app_applicationmodel.property.app_rejectStep.title=\u5374\u4e0b\u30b9\u30c6\u30c3\u30d7 +app_applicationmodel.property.app_rejectStep.description=\u5374\u4e0b\u30b9\u30c6\u30c3\u30d7 +app_applicationmodel.property.app_rejectFolder.title=\u5374\u4e0b\u30d5\u30a9\u30eb\u30c0 +app_applicationmodel.property.app_rejectFolder.description=\u5374\u4e0b\u30d5\u30a9\u30eb\u30c0 +app_applicationmodel.property.app_rejectMove.title=\u79fb\u52d5\u307e\u305f\u306f\u30b3\u30d4\u30fc +app_applicationmodel.property.app_rejectMove.description=\u79fb\u52d5\u307e\u305f\u306f\u30b3\u30d4\u30fc + +app_applicationmodel.aspect.app_configurable.title=\u8a2d\u5b9a\u53ef\u80fd +app_applicationmodel.aspect.app_configurable.description=\u8a2d\u5b9a\u53ef\u80fd +app_applicationmodel.association.app_configurations.title=\u8a2d\u5b9a +app_applicationmodel.association.app_configurations.description=\u8a2d\u5b9a diff --git a/config/alfresco/messages/avm-messages_es.properties b/config/alfresco/messages/avm-messages_es.properties index c4bfef162b..906e5e52d5 100755 --- a/config/alfresco/messages/avm-messages_es.properties +++ b/config/alfresco/messages/avm-messages_es.properties @@ -1,7 +1,7 @@ # AVM related messages expiredcontent.workflow.title=Contenido vencido en ''{0}'' -avmlockservice.locked=Usted no tiene acceso a ''{0}''; actualmente est\u00e1 bloqueado por el usuario {1}. +avmlockservice.locked=Usted no tiene acceso a ''{0}''; actualmente est\u00e1 bloqueado por el usuario ''{1}''. testserver.taken=El servidor de test ''{0}'' que ha seleccionado ha sido asignado a otro usuario, si es posible, seleccione un servidor diferente y vuelva a intentar. avm.cycle.create=El ciclo ser\u00eda creado. diff --git a/config/alfresco/messages/avm-messages_it.properties b/config/alfresco/messages/avm-messages_it.properties index db58ddb68a..4d92ce1638 100755 --- a/config/alfresco/messages/avm-messages_it.properties +++ b/config/alfresco/messages/avm-messages_it.properties @@ -2,7 +2,7 @@ expiredcontent.workflow.title=Contenuto scaduto in ''{0}'' avmlockservice.locked=Non si dispone dell'accesso a ''{0}''; attualmente \u00e8 bloccato dall'utente ''{1}''. -testserver.taken=Il server di test ''{0}'' selezionato \u00e8 stato assegnato a un altro utente. Se possibile, selezionare un server diverso e riprovare. +testserver.taken=Il server di test ''{0}'' selezionato \u00e8 stato assegnato a un altro utente, se possibile, selezionare un server diverso e riprovare. avm.cycle.create=Verrebbe creato un ciclo. avm.cycle.lookup=Ciclo nella ricerca. diff --git a/config/alfresco/messages/avm-messages_ja.properties b/config/alfresco/messages/avm-messages_ja.properties new file mode 100755 index 0000000000..eca275341b --- /dev/null +++ b/config/alfresco/messages/avm-messages_ja.properties @@ -0,0 +1,8 @@ +# AVM related messages + +expiredcontent.workflow.title=''{0}''\u5185\u306e\u671f\u9650\u5207\u308c\u30b3\u30f3\u30c6\u30f3\u30c4 +avmlockservice.locked=''{0}'' \u3078\u306e\u30a2\u30af\u30bb\u30b9\u6a29\u9650\u304c\u3042\u308a\u307e\u305b\u3093\u3002\u73fe\u5728\u30e6\u30fc\u30b6 ''{1}'' \u306b\u3088\u3063\u3066\u30ed\u30c3\u30af\u3055\u308c\u3066\u3044\u307e\u3059\u3002 +testserver.taken=\u9078\u629e\u3057\u305f\u30c6\u30b9\u30c8\u30b5\u30fc\u30d0''{0}''\u306f\u4ed6\u306e\u30e6\u30fc\u30b6\u306b\u3088\u3063\u3066\u5272\u308a\u5f53\u3066\u3089\u308c\u3066\u3044\u307e\u3059\u3001\u53ef\u80fd\u3067\u3042\u308c\u3070\u4ed6\u306e\u30b5\u30fc\u30d0\u3092\u9078\u629e\u3057\u3066\u518d\u8a66\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +avm.cycle.create=\u30b5\u30a4\u30af\u30eb\u304c\u4f5c\u6210\u3055\u308c\u307e\u3059\u3002 +avm.cycle.lookup=\u30b5\u30a4\u30af\u30eb\u306f\u691c\u67fb\u4e2d\u3067\u3059\u3002 diff --git a/config/alfresco/messages/bootstrap-content-template-examples_it.properties b/config/alfresco/messages/bootstrap-content-template-examples_it.properties index cffc15b9ea..3dbff22c04 100755 --- a/config/alfresco/messages/bootstrap-content-template-examples_it.properties +++ b/config/alfresco/messages/bootstrap-content-template-examples_it.properties @@ -1,4 +1,4 @@ -content.template.calculates_if_the_document=Calcola se al documento \u00e8 applicato l'aspetto Localizable +content.template.calculates_if_the_document=Calcola se al documento \u00e8 applicato l'aspetto localizable content.template.displays_useful_information=Visualizza informazioni utili sul documento attuale content.template.displays_a_list_of_the_documents=Visualizza l'elenco dei documenti presenti nello spazio di homepage dell'utente attuale content.template.displays_a_list_of_spaces=Visualizza l'elenco degli spazi presenti nello spazio di homepage dell'utente attuale diff --git a/config/alfresco/messages/bootstrap-content-template-examples_ja.properties b/config/alfresco/messages/bootstrap-content-template-examples_ja.properties new file mode 100755 index 0000000000..0fe02a113c --- /dev/null +++ b/config/alfresco/messages/bootstrap-content-template-examples_ja.properties @@ -0,0 +1,14 @@ +content.template.calculates_if_the_document=\u6587\u66f8\u306b\u30ed\u30fc\u30ab\u30e9\u30a4\u30ba\u53ef\u80fd\u306a\u30a2\u30b9\u30da\u30af\u30c8\u304c\u9069\u7528\u3055\u308c\u308b\u5834\u5408\u306b\u8a08\u7b97\u3057\u307e\u3059\u3002 +content.template.displays_useful_information=\u73fe\u5728\u306e\u6587\u66f8\u306b\u95a2\u3059\u308b\u6709\u7528\u306a\u60c5\u5831\u3092\u8868\u793a\u3057\u307e\u3059\u3002 +content.template.displays_a_list_of_the_documents=\u73fe\u5728\u306e\u30e6\u30fc\u30b6\u306e\u30db\u30fc\u30e0\u30b9\u30da\u30fc\u30b9\u306b\u306e\u6587\u66f8\u306e\u30ea\u30b9\u30c8\u3092\u8868\u793a\u3057\u307e\u3059\u3002 +content.template.displays_a_list_of_spaces=\u73fe\u5728\u306e\u30e6\u30fc\u30b6\u306e\u30db\u30fc\u30e0\u30b9\u30da\u30fc\u30b9\u306b\u30b9\u30da\u30fc\u30b9\u306e\u30ea\u30b9\u30c8\u3092\u8868\u793a\u3057\u307e\u3059\u3002 +content.template.shows_a_simple_summary_page=\u73fe\u5728\u306e\u30e6\u30fc\u30b6\u3068\u305d\u306e\u30db\u30fc\u30e0\u30b9\u30da\u30fc\u30b9\u306b\u95a2\u3059\u308b\u5358\u7d14\u306a\u8981\u7d04\u30da\u30fc\u30b8\u3092\u8868\u793a\u3057\u307e\u3059\u3002 +content.template.calculates_if_the_document_has=\u6587\u66f8\u304c\u7ffb\u8a33\u53ef\u80fd\u30a2\u30b9\u30da\u30af\u30c8\u3092\u9069\u7528\u3057\u305f\u5834\u5408\u306b\u8a08\u7b97\u3057\u307e\u3059\u3002 +content.template.displays_a_list_of_the_documents_in_the_current_space=\u904e\u53bb7\u65e5\u4ee5\u5185\u306b\u4f5c\u6210\u307e\u305f\u306f\u4fee\u6b63\u3055\u308c\u305f\u73fe\u5728\u306e\u30b9\u30da\u30fc\u30b9\u306b\u3042\u308b\u6587\u66f8\u306e\u30ea\u30b9\u30c8\u3092\u8868\u793a\u3057\u307e\u3059\u3002 +content.template.example_of_various_lists=\u73fe\u5728\u306e\u30e6\u30fc\u30b6\u306b\u95a2\u3059\u308b\u6587\u66f8\u3001\u30b9\u30da\u30fc\u30b9\u3001\u8981\u7d04\u60c5\u5831\u306e\u3055\u307e\u3056\u307e\u306a\u30ea\u30b9\u30c8\u306e\u30b5\u30f3\u30d7\u30eb +content.template.displays_a_list_of_the_documents_in_the_current_user_Home_Space=\u73fe\u5728\u306e\u30e6\u30fc\u30b6\u306e\u30db\u30fc\u30e0\u30b9\u30da\u30fc\u30b9\u306b\u6587\u66f8\u306e\u30ea\u30b9\u30c8\u3092\u8868\u793a\u3057\u307e\u3059\u3002 JPEG\u30b3\u30f3\u30c6\u30f3\u30c4\u306f\u5c0f\u3055\u306a\u30b5\u30e0\u30cd\u30a4\u30eb\u753b\u50cf\u306b\u306a\u308b\u305f\u3081\u3001\u30c6\u30ad\u30b9\u30c8\u6587\u66f8\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u30a4\u30f3\u30e9\u30a4\u30f3\u8868\u793a\u3055\u308c\u307e\u3059\u3002 +content.template.displays_the_audit_trail=\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306e\u30aa\u30fc\u30c7\u30a3\u30c3\u30c8\u30fb\u30c8\u30ec\u30fc\u30eb\u3092\u8868\u793a\u3057\u307e\u3059\u3002 +email.template.email_template_for_notifying_users=\u30eb\u30fc\u30eb\u3084\u30a2\u30af\u30b7\u30e7\u30f3\u304b\u3089\u30e6\u30fc\u30b6\u306b\u901a\u77e5\u3059\u308b\u305f\u3081\u306eE\u30e1\u30fc\u30eb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +email.template.email_template_for_notifying_users_of_an_Invite=\u30b9\u30da\u30fc\u30b9\u3084\u6587\u66f8\u306b\u3001\u62db\u5f85\u3092\u884c\u3063\u305f\u3053\u3068\u3092\u30e6\u30fc\u30b6\u306b\u901a\u77e5\u3059\u308bE\u30e1\u30fc\u30eb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +rss.template.renders_a_valid_rss=\u6709\u52b9\u306aRSS2.0 XML\u6587\u66f8\u3092\u30ec\u30f3\u30c0\u30ea\u30f3\u30b0\u3057\u3066\u3001\u904e\u53bb7\u65e5\u4ee5\u5185\u306b\u4f5c\u6210\u307e\u305f\u306f\u4fee\u6b63\u3055\u308c\u305f\u73fe\u5728\u306e\u30b9\u30da\u30fc\u30b9\u306b\u6587\u66f8\u3092\u8868\u793a\u3057\u307e\u3059\u3002 \u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u4f7f\u7528\u3059\u308b\u5834\u5408\u306f\u3001\u3042\u3089\u304b\u3058\u3081\u9069\u5207\u306a\u30b5\u30fc\u30d0\u3068\u30dd\u30fc\u30c8\u3092\u4f7f\u7528\u3059\u308b\u3088\u3046\u306b\u8a2d\u5b9a\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 +rss.template.rss_recent_docs=RSS \u6700\u8fd1\u306e\u6587\u66f8 diff --git a/config/alfresco/messages/bootstrap-example-javascripts_ja.properties b/config/alfresco/messages/bootstrap-example-javascripts_ja.properties new file mode 100755 index 0000000000..81a7ed3db6 --- /dev/null +++ b/config/alfresco/messages/bootstrap-example-javascripts_ja.properties @@ -0,0 +1,14 @@ +# Javascript Examples Localization + +javascripts.example.backup.title=\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u30fb\u30b9\u30af\u30ea\u30d7\u30c8 +javascripts.example.backup.description=\u5358\u7d14\u306a\u6587\u66f8\u306e\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u306e\u30b9\u30af\u30ea\u30d7\u30c8 +javascripts.example.test.title=\u30b5\u30f3\u30d7\u30eb\u306e\u30c6\u30b9\u30c8\u30b9\u30af\u30ea\u30d7\u30c8 +javascripts.example.test.description=\u3055\u307e\u3056\u307e\u306aAPI\u30b3\u30fc\u30eb\u306e\u30b5\u30f3\u30d7\u30eb +javascripts.example.backupandlog.title=\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u3068\u30ed\u30ae\u30f3\u30b0\u30fb\u30b9\u30af\u30ea\u30d7\u30c8 +javascripts.example.backupandlog.description=\u30d5\u30a1\u30a4\u30eb\u3092\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u3057\u65e5\u4ed8\u3068\u6642\u523b\u3092\u30ed\u30b0\u3057\u307e\u3059 +javascripts.example.appendcopyright.title=Copyright\u3092\u30d5\u30a1\u30a4\u30eb\u306b\u8ffd\u52a0\u3057\u307e\u3059 +javascripts.example.appendcopyright.description=\u30c6\u30ad\u30b9\u30c8\u307e\u305f\u306fHTML\u30d5\u30a1\u30a4\u30eb\u306bCopyright\u884c\u3092\u8ffd\u52a0\u3057\u307e\u3059 +javascripts.example.testreturnvalue.title=\u623b\u308a\u5024\u306e\u30b5\u30f3\u30d7\u30eb +javascripts.example.testreturnvalue.description=\u30b3\u30de\u30f3\u30c9\u30fb\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306e\u305f\u3081\u306e\u3001\u30b9\u30af\u30ea\u30d7\u30c8\u304b\u3089\u5024\u3092\u623b\u3057\u307e\u3059 +javascripts.example.alfrescodocs.title=Alfresco Lucene\u691c\u7d22 +javascripts.example.alfrescodocs.description=Alfresco\u30c6\u30ad\u30b9\u30c8\u306b\u683c\u7d0d\u3055\u308c\u3066\u3044\u308b\u3059\u3079\u3066\u306e\u6587\u66f8\u3092\u691c\u7d22\u3057\u30ed\u30b0\u3057\u307e\u3059 diff --git a/config/alfresco/messages/bootstrap-imapScripts_ja.properties b/config/alfresco/messages/bootstrap-imapScripts_ja.properties new file mode 100755 index 0000000000..f3ea9d0221 --- /dev/null +++ b/config/alfresco/messages/bootstrap-imapScripts_ja.properties @@ -0,0 +1,12 @@ +imap.command_processor.name=command-processor.js +imap.command_processor.title=\u30b3\u30de\u30f3\u30c9\u30fb\u30d7\u30ed\u30bb\u30c3\u30b5 +imap.command_processor.description=E\u30e1\u30fc\u30eb\u30fb\u30b3\u30de\u30f3\u30c9\u30fb\u30d7\u30ed\u30bb\u30c3\u30b5\u30fb\u30b9\u30af\u30ea\u30d7\u30c8 + +imap.command_search.name=command-search.js +imap.command_search.title=\u691c\u7d22\u30b3\u30de\u30f3\u30c9 +imap.command_search.description=E\u30e1\u30fc\u30eb\u30fb\u691c\u7d22\u30b3\u30de\u30f3\u30c9\u30fb\u30b9\u30af\u30ea\u30d7\u30c8 + +imap.command_utils.name=command-utils.js +imap.command_utils.title=\u30b3\u30de\u30f3\u30c9\u30fb\u30e6\u30fc\u30c6\u30a3\u30ea\u30c6\u30a3 +imap.command_utils.description=E\u30e1\u30fc\u30eb\u30fb\u30b3\u30de\u30f3\u30c9\u30fb\u30e6\u30fc\u30c6\u30a3\u30ea\u30c6\u30a3 + diff --git a/config/alfresco/messages/bootstrap-javascripts_ja.properties b/config/alfresco/messages/bootstrap-javascripts_ja.properties new file mode 100755 index 0000000000..e31b844caa --- /dev/null +++ b/config/alfresco/messages/bootstrap-javascripts_ja.properties @@ -0,0 +1,17 @@ +javascripts.backup.description=\u5358\u7d14\u306a\u6587\u66f8\u306e\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u306e\u30b9\u30af\u30ea\u30d7\u30c8 +javascripts.backup.title=\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u30b9\u30af\u30ea\u30d7\u30c8 + +javascripts.examle_test.description=Alfresco API\u30b3\u30fc\u30eb\u306e\u30b5\u30f3\u30d7\u30eb +javascripts.examle_test.title=\u30b5\u30f3\u30d7\u30eb\u306e\u30c6\u30b9\u30c8\u30fb\u30b9\u30af\u30ea\u30d7\u30c8 + +javascripts.backup_and_log.description=\u30d5\u30a1\u30a4\u30eb\u3092\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u3057\u65e5\u4ed8\u3068\u6642\u523b\u3092\u30ed\u30b0\u3057\u307e\u3059 +javascripts.backup_and_log.title=\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u3068\u30ed\u30ae\u30f3\u30b0\u30fb\u30b9\u30af\u30ea\u30d7\u30c8 + +javascripts.append_copyright.description=\u30c6\u30ad\u30b9\u30c8\u307e\u305f\u306fHTML\u30d5\u30a1\u30a4\u30eb\u306bCopyright\u884c\u3092\u8ffd\u52a0\u3057\u307e\u3059 +javascripts.append_copyright.title=Copyright\u3092\u30d5\u30a1\u30a4\u30eb\u306b\u8ffd\u52a0\u3057\u307e\u3059 + +javascripts.lucene_search.description=Alfresco\u30c6\u30ad\u30b9\u30c8\u306b\u683c\u7d0d\u3055\u308c\u3066\u3044\u308b\u3059\u3079\u3066\u306e\u6587\u66f8\u3092\u691c\u7d22\u3057\u30ed\u30b0\u3057\u307e\u3059 +javascripts.lucene_search.title=Alfresco Lucene\u691c\u7d22 + +javascripts.return_value.description=\u30b3\u30de\u30f3\u30c9\u30fb\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306e\u305f\u3081\u306e\u3001\u30b9\u30af\u30ea\u30d7\u30c8\u304b\u3089\u5024\u3092\u623b\u3057\u307e\u3059 +javascripts.return_value.title=\u623b\u308a\u5024\u306e\u30b5\u30f3\u30d7\u30eb diff --git a/config/alfresco/messages/bootstrap-readme-template_ja.properties b/config/alfresco/messages/bootstrap-readme-template_ja.properties new file mode 100755 index 0000000000..e30e70aca9 --- /dev/null +++ b/config/alfresco/messages/bootstrap-readme-template_ja.properties @@ -0,0 +1,2 @@ +readme.template.description=Readme\u30d5\u30a1\u30a4\u30eb(\u3064\u307e\u308a\u3001readme.html\u307e\u305f\u306freadme.ftl)\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u3092\u8868\u793a\u3057\u307e\u3059\u3002 +readme.template.title=readme.ftl diff --git a/config/alfresco/messages/bootstrap-spaces.properties b/config/alfresco/messages/bootstrap-spaces.properties index e4076965a3..b2a5d56714 100644 --- a/config/alfresco/messages/bootstrap-spaces.properties +++ b/config/alfresco/messages/bootstrap-spaces.properties @@ -12,6 +12,9 @@ spaces.imapConfig.description=Imap Configs spaces.imap_templates.name=Templates spaces.imap_templates.description=Templates for IMAP generated messages +spaces.imap_templates.emailbody_textplain.description=Email template used to generate the "multipart/alternative" IMAP message body ("text/plain" part). +spaces.imap_templates.emailbody_texthtml.description=Email template used to generate the "multipart/alternative" IMAP message body ("text/html" part). + spaces.emailActions.name=Email Actions spaces.emailActions.description=Email Actions @@ -54,6 +57,10 @@ spaces.guest_home.description=The guest root space spaces.scripts.name=Scripts spaces.scripts.description=JavaScript files +spaces.scripts.example.workflow.name=start-pooled-review-workflow.js +spaces.scripts.example.workflow.title=Start Pooled Review and Approve Workflow +spaces.scripts.example.workflow.description=Starts the Pooled Review and Approve workflow for all members of the site the document belongs to + spaces.wcm.name=Web Projects spaces.wcm.description=Web Content Management Spaces @@ -84,8 +91,19 @@ spaces.templates.email.notify.description=Notify Email Templates spaces.templates.email.generate_the_invite_email.description=Email template used to generate the invite email for Alfresco Share email.template.email_template_for_notifying_users=Email template for notifying users from a rule or action + +email.template.email_template_for_notifying_users.sample=Sample Email template for notifying users from a rule or action email.template.email_template_for_notifying_users_of_an_Invite=Email template for notifying users of an Invite to a space or document +email.templates.email_template_for_notifying_new_users=Email template used to inform new users of their accounts + +version.default=Default version +version.french=French version +version.german=German version +version.italian=Italian version +version.japanese=Japanese version +version.spanish=Spanish version + spaces.web.client.extension.name=Web Client Extension spaces.web.client.extension.title=Customized Web Client spaces.web.client.extension.description=Customized Web Client @@ -102,3 +120,27 @@ spaces.workflow.definitions.name=Workflow Definitions spaces.workflow.definitions.title=Customized Workflow Process Definitions spaces.workflow.definitions.description=Customized Workflow Process Definitions +spaces.templates.email.activities.name=activities +spaces.templates.email.activities.description=Activities email templates + +spaces.templates.email.generate_the_activities_email.description=Email template used to generate the activities email for Alfresco Share + +spaces.transfers.name=Transfers +spaces.transfers.title=Transfers +spaces.transfers.description=Folder used by the Transfer subsystem + +spaces.transfer_groups.name=Transfer Target Groups +spaces.transfer_groups.title=Transfer Target Groups +spaces.transfer_groups.description=Folder containing groups of transfer targets + +spaces.transfer_groups_default.name=Default Group +spaces.transfer_groups_default.title=Default Group +spaces.transfer_groups_default.description=Put your transfer targets in this folder + +spaces.transfer_temp.name=Temp +spaces.transfer_temp.title=Temp +spaces.transfer_temp.description=Folder to store temporary nodes during transfer + +spaces.inbound_transfer_records.name=Inbound Transfer Records +spaces.inbound_transfer_records.title=Inbound Transfer Records +spaces.inbound_transfer_records.description=Folder containing records of inbound transfers diff --git a/config/alfresco/messages/bootstrap-spaces_de.properties b/config/alfresco/messages/bootstrap-spaces_de.properties index 18b36ecaf4..87cda3c5f2 100755 --- a/config/alfresco/messages/bootstrap-spaces_de.properties +++ b/config/alfresco/messages/bootstrap-spaces_de.properties @@ -1,8 +1,8 @@ -## Labels used in bootstrap Space definitions +# Labels used in bootstrap Space definitions spaces.company_home.name=Firmen-Home spaces.company_home.description=Root-Raum der Firma - + spaces.dictionary.name=Datenverzeichnis spaces.dictionary.description=Benutzerdefinierte Definitionen @@ -12,6 +12,9 @@ spaces.imapConfig.description=Imap Configs spaces.imap_templates.name=Mustervorlagen spaces.imap_templates.description=Mustervorlage f\u00fcr IMAP-generierte Nachrichten +spaces.imap_templates.emailbody_textplain.description=E-Mail-Mustervorlage, die verwendet wird, um einen "multipart/alternative" IMAP-Nachrichtenk\u00f6rper zu erstellen ("Text/Nur Text"-Teil). +spaces.imap_templates.emailbody_texthtml.description=E-Mail-Mustervorlage, die verwendet wird, um einen "multipart/alternative" IMAP-Nachrichtenk\u00f6rper zu erstellen ("Text/HTML"-Teil). + spaces.emailActions.name=E-Mail-Aktionen spaces.emailActions.description=E-Mail-Aktionen @@ -24,8 +27,8 @@ spaces.templates.description=Mustervorlagen Raum-Ordner spaces.templates.content.name=Pr\u00e4sentations-Mustervorlagen spaces.templates.content.description=Pr\u00e4sentations-Mustervorlagen -spaces.templates.email.name=E-Mail Mustervorlagen -spaces.templates.email.description=E-Mail Mustervorlagen +spaces.templates.email.name=E-Mail-Mustervorlagen +spaces.templates.email.description=E-Mail-Mustervorlagen spaces.invite_templates.email.name=E-Mail Einladungs-Mustervorlagen spaces.invite_templates.email.description=E-Mail Einladungs-Mustervorlagen @@ -36,6 +39,9 @@ spaces.notify_templates.email.description=E-Mail Benachrichtigungs-Mustervorlage spaces.templates.rss.name=RSS Mustervorlagen spaces.templates.rss.description=RSS Mustervorlagen +spaces.actions.scheduled_actions.name=Geplante Aktionen +spaces.actions.scheduled_actions.description=Ablaufplan f\u00fcr st\u00e4ndig auszuf\u00fchrende Aktionen + spaces.rendition.rendering_actions.name=Arbeitsbereich f\u00fcr Renderingaktionen spaces.rendition.rendering_actions.description=Ein von dem System verwendeter Raum zur Umsetzung von Rendering-Aktionen. @@ -51,6 +57,10 @@ spaces.guest_home.description=Root Arbeitsbereich f\u00fcr G\u00e4ste spaces.scripts.name=Skripte spaces.scripts.description=JavaScript Dateien +spaces.scripts.example.workflow.name=start-pooled-review-workflow.js +spaces.scripts.example.workflow.title=Geb\u00fcndelte \u00dcberpr\u00fcfung starten und Workflow genehmigen +spaces.scripts.example.workflow.description=Startet die geb\u00fcndelte \u00dcberpr\u00fcfung und genehmigt den Workflow f\u00fcr alle Mitglieder der Site, zu der das Dokument geh\u00f6rt + spaces.wcm.name=Web Projekte spaces.wcm.description=Web Content Management R\u00e4ume @@ -67,7 +77,7 @@ spaces.sites.name=Sites spaces.sites.description=Collaboration Site Arbeitsr\u00e4ume spaces.templates.email.invite.name=einladen -spaces.templates.email.invite.description=E-Mail Mustervorlagen f\u00fcr Einladungen +spaces.templates.email.invite.description=E-Mail-Mustervorlagen f\u00fcr Einladungen spaces.imap_home.name=IMAP Home spaces.imap_home.description=IMAP Home @@ -80,8 +90,19 @@ spaces.templates.email.notify.description=E-Mail Benachrichtigungs-Mustervorlage spaces.templates.email.generate_the_invite_email.description=E-Mail Vorlage zur Generierung der Einladungsmail f\u00fcr Alfresco Share -email.template.email_template_for_notifying_users=E-Mail Mustervorlage f\u00fcr Benachrichtigung der Benutzer \u00fcber eine Regel oder Aktion -email.template.email_template_for_notifying_users_of_an_Invite=E-Mail Mustervorlage zur Benachrichtigung der Benutzer \u00fcber eine Einladung in einen Raum oder zu einem Dokument +email.template.email_template_for_notifying_users=E-Mail-Mustervorlage zur Benachrichtigung der Benutzer \u00fcber eine Regel oder Aktion + +email.template.email_template_for_notifying_users.sample=Beispiel f\u00fcr E-Mail-Mustervorlage zur Benachrichtigung der Benutzer \u00fcber eine Regel oder Aktion +email.template.email_template_for_notifying_users_of_an_Invite=E-Mail-Mustervorlage zur Benachrichtigung der Benutzer \u00fcber eine Einladung in einen Raum oder zu einem Dokument + +email.templates.email_template_for_notifying_new_users=E-Mail-Mustervorlage zur Benachrichtigung neuer Benutzer \u00fcber ihre Konten + +version.default=Standardversion +version.french=Franz\u00f6sische Version +version.german=Deutsche Version +version.italian=Italienische Version +version.japanese=Japanische Version +version.spanish=Spanische Version spaces.web.client.extension.name=Web Client Erweiterung spaces.web.client.extension.title=Parametrierter Web Client @@ -96,5 +117,30 @@ spaces.messages.title=Parametrierte Nachrichten spaces.messages.description=Parametrierte Nachrichten spaces.workflow.definitions.name=Workflowdefinitionen -spaces.workflow.definitions.title=Parametrierte Definitionen f\u00fcr den Arbeitsablaufsprozess -spaces.workflow.definitions.description=Parametrierte Definitionen f\u00fcr den Arbeitsablaufsprozess \ No newline at end of file +spaces.workflow.definitions.title=Parametrierte Definitionen f\u00fcr Workflow-Prozess +spaces.workflow.definitions.description=Parametrierte Definitionen f\u00fcr Workflow-Prozess + +spaces.templates.email.activities.name=Aktivit\u00e4ten +spaces.templates.email.activities.description=E-Mail-Mustervorlage f\u00fcr Aktivit\u00e4ten + +spaces.templates.email.generate_the_activities_email.description=E-Mail-Mustervorlage zur Generierung der Aktivit\u00e4tsmail f\u00fcr Alfresco Share + +spaces.transfers.name=\u00dcbertragungen +spaces.transfers.title=\u00dcbertragungen +spaces.transfers.description=Vom \u00dcbertragungssubsystem verwendeter Ordner + +spaces.transfer_groups.name=Zielgruppen \u00fcbertragen +spaces.transfer_groups.title=Zielgruppen \u00fcbertragen +spaces.transfer_groups.description=Ordner, der Gruppen aus \u00dcbertragungszielen enth\u00e4lt + +spaces.transfer_groups_default.name=Standardgruppe +spaces.transfer_groups_default.title=Standardgruppe +spaces.transfer_groups_default.description=Legen Sie \u00dcbertragungsziele in diesen Ordner + +spaces.transfer_temp.name=Temp +spaces.transfer_temp.title=Temp +spaces.transfer_temp.description=Ordner, der die tempor\u00e4ren Knoten w\u00e4hrend der \u00dcbertragung speichert + +spaces.inbound_transfer_records.name=Eingehende \u00dcbertragungsdatens\u00e4tze +spaces.inbound_transfer_records.title=Eingehende \u00dcbertragungsdatens\u00e4tze +spaces.inbound_transfer_records.description=Ordner, der Datens\u00e4tze eingehender \u00dcbertragungen enth\u00e4lt diff --git a/config/alfresco/messages/bootstrap-spaces_es.properties b/config/alfresco/messages/bootstrap-spaces_es.properties index 30c1bf25c7..f00224813e 100755 --- a/config/alfresco/messages/bootstrap-spaces_es.properties +++ b/config/alfresco/messages/bootstrap-spaces_es.properties @@ -12,6 +12,9 @@ spaces.imapConfig.description=Configuraciones IMAP spaces.imap_templates.name=Plantillas spaces.imap_templates.description=Plantillas para mensajes generados por IMAP +spaces.imap_templates.emailbody_textplain.description=Plantilla de correo electr\u00f3nico utilizada para generar el cuerpo del mensaje de IMAP "de varias partes/alternativo" (parte de "texto/sin formato"). +spaces.imap_templates.emailbody_texthtml.description=Plantilla de correo electr\u00f3nico utilizada para generar el cuerpo del mensaje de IMAP "de varias partes/alternativo" (parte de "texto/html"). + spaces.emailActions.name=Acciones de correo electr\u00f3nico spaces.emailActions.description=Acciones de correo electr\u00f3nico @@ -27,8 +30,8 @@ spaces.templates.content.description=Plantillas de presentaci\u00f3n spaces.templates.email.name=Plantillas de correo electr\u00f3nico spaces.templates.email.description=Plantillas de correo electr\u00f3nico -spaces.invite_templates.email.name=Plantillas de invitaci\u00f3n por correo eletr\u00f3nico -spaces.invite_templates.email.description=Plantillas de invitaci\u00f3n por correo eletr\u00f3nico +spaces.invite_templates.email.name=Plantillas de invitaci\u00f3n por correo electr\u00f3nico +spaces.invite_templates.email.description=Plantillas de invitaci\u00f3n por correo electr\u00f3nico spaces.notify_templates.email.name=Plantillas de notificaci\u00f3n por correo electr\u00f3nico spaces.notify_templates.email.description=Plantillas de notificaci\u00f3n por correo electr\u00f3nico @@ -36,6 +39,9 @@ spaces.notify_templates.email.description=Plantillas de notificaci\u00f3n por co spaces.templates.rss.name=Plantillas RSS spaces.templates.rss.description=Plantillas RSS +spaces.actions.scheduled_actions.name=Acciones programadas +spaces.actions.scheduled_actions.description=Programaci\u00f3n de cu\u00e1ndo se realizan las acciones persistentes + spaces.rendition.rendering_actions.name=Espacio de renderizaci\u00f3n de acciones spaces.rendition.rendering_actions.description=Un espacio utilizado por el sistema para la persistencia de acciones de renderizaci\u00f3n. @@ -51,6 +57,10 @@ spaces.guest_home.description=El espacio ra\u00edz de invitado spaces.scripts.name=Scripts spaces.scripts.description=Ficheros JavaScript +spaces.scripts.example.workflow.name=start-pooled-review-workflow.js +spaces.scripts.example.workflow.title=Iniciar flujo de trabajo de revisi\u00f3n y aprobaci\u00f3n en conjunto +spaces.scripts.example.workflow.description=Inicia el flujo de trabajo de revisi\u00f3n y aprobaci\u00f3n en conjunto para todos los miembros del sitio al que pertenece el documento + spaces.wcm.name=Proyectos Web spaces.wcm.description=Espacios Web Content Management @@ -81,8 +91,19 @@ spaces.templates.email.notify.description=Plantillas de correo electr\u00f3nico spaces.templates.email.generate_the_invite_email.description=Plantilla correo electr\u00f3nico utilizada para generar el correo electr\u00f3nico de invitaci\u00f3n a Alfresco Share email.template.email_template_for_notifying_users=Plantilla de correo electr\u00f3nico para notificar a los usuarios una regla o acci\u00f3n + +email.template.email_template_for_notifying_users.sample=Muestra de plantilla de correo electr\u00f3nico para notificar a los usuarios una regla o acci\u00f3n email.template.email_template_for_notifying_users_of_an_Invite=Plantilla de correo electr\u00f3nico para notificar a los usuarios una invitaci\u00f3n a un espacio o documento +email.templates.email_template_for_notifying_new_users=Plantilla de correo electr\u00f3nico utilizada para informar a los nuevos usuarios sobre sus cuentas + +version.default=Versi\u00f3n por defecto +version.french=Versi\u00f3n en franc\u00e9s +version.german=Versi\u00f3n en alem\u00e1n +version.italian=Versi\u00f3n en italiano +version.japanese=Versi\u00f3n en japon\u00e9s +version.spanish=Versi\u00f3n en espa\u00f1ol + spaces.web.client.extension.name=Extensi\u00f3n de cliente Web spaces.web.client.extension.title=Cliente Web personalizado spaces.web.client.extension.description=Cliente Web personalizado @@ -98,3 +119,28 @@ spaces.messages.description=Mensajes personalizados spaces.workflow.definitions.name=Definiciones de flujo de trabajo spaces.workflow.definitions.title=Definiciones personalizadas de procesos de flujo de trabajo spaces.workflow.definitions.description=Definiciones personalizadas de procesos de flujo de trabajo + +spaces.templates.email.activities.name=actividades +spaces.templates.email.activities.description=Plantillas de correo electr\u00f3nico de actividades + +spaces.templates.email.generate_the_activities_email.description=Plantilla utilizada para generar el correo electr\u00f3nico de actividades de Alfresco Share + +spaces.transfers.name=Transferencias +spaces.transfers.title=Transferencias +spaces.transfers.description=Carpeta utilizada por el subsistema de transferencia + +spaces.transfer_groups.name=Grupos de destino de transferencia +spaces.transfer_groups.title=Grupos de destino de transferencia +spaces.transfer_groups.description=Carpeta que contiene grupos de destino de transferencias + +spaces.transfer_groups_default.name=Grupo por defecto +spaces.transfer_groups_default.title=Grupo por defecto +spaces.transfer_groups_default.description=Coloque los destinos de sus transferencias en esta carpeta + +spaces.transfer_temp.name=Temporal +spaces.transfer_temp.title=Temporal +spaces.transfer_temp.description=Carpeta para almacenar nodos temporales durante las transferencias + +spaces.inbound_transfer_records.name=Registros de transferencias entrantes +spaces.inbound_transfer_records.title=Registros de transferencias entrantes +spaces.inbound_transfer_records.description=Carpeta que contiene registros de transferencias entrantes diff --git a/config/alfresco/messages/bootstrap-spaces_fr.properties b/config/alfresco/messages/bootstrap-spaces_fr.properties index 7b23fbab9b..9e71f6d743 100755 --- a/config/alfresco/messages/bootstrap-spaces_fr.properties +++ b/config/alfresco/messages/bootstrap-spaces_fr.properties @@ -1,22 +1,25 @@ # Labels used in bootstrap Space definitions spaces.company_home.name=Espace racine -spaces.company_home.description=L'espace racine de la soci\u00e9t\u00e9 +spaces.company_home.description=Espace racine de la soci\u00e9t\u00e9 spaces.dictionary.name=Dictionnaire de donn\u00e9es spaces.dictionary.description=D\u00e9finitions personnalis\u00e9es -spaces.imapConfig.name=Configurations\u00a0IMAP -spaces.imapConfig.description=Configurations\u00a0IMAP +spaces.imapConfig.name=Configurations IMAP +spaces.imapConfig.description=Configurations IMAP spaces.imap_templates.name=Mod\u00e8les spaces.imap_templates.description=Mod\u00e8les pour les messages g\u00e9n\u00e9r\u00e9s par IMAP +spaces.imap_templates.emailbody_textplain.description=Mod\u00e8le d'e-mail utilis\u00e9 pour g\u00e9n\u00e9rer le corps du message IMAP "multipart/alternative" (partie "text/plain"). +spaces.imap_templates.emailbody_texthtml.description=Mod\u00e8le d'e-mail utilis\u00e9 pour g\u00e9n\u00e9rer le corps du message IMAP "multipart/alternative" (partie "text/html"). + spaces.emailActions.name=Actions d'e-mail spaces.emailActions.description=Actions d'e-mail -spaces.searchAction.name=recherche -spaces.searchAction.description=recherche +spaces.searchAction.name=Recherche +spaces.searchAction.description=Recherche spaces.templates.name=Mod\u00e8les d'espace spaces.templates.description=Mod\u00e8les de dossier d'espace @@ -33,8 +36,11 @@ spaces.invite_templates.email.description=Mod\u00e8les d'invitation par e-mail spaces.notify_templates.email.name=Mod\u00e8les de notification par e-mail spaces.notify_templates.email.description=Mod\u00e8les de notification par e-mail -spaces.templates.rss.name=Mod\u00e8les\u00a0RSS -spaces.templates.rss.description=Mod\u00e8les\u00a0RSS +spaces.templates.rss.name=Mod\u00e8les RSS +spaces.templates.rss.description=Mod\u00e8les RSS + +spaces.actions.scheduled_actions.name=Actions planifi\u00e9es +spaces.actions.scheduled_actions.description=Planning d'ex\u00e9cution des actions persistantes spaces.rendition.rendering_actions.name=Espace d'actions de rendu spaces.rendition.rendering_actions.description=Espace utilis\u00e9 par le syst\u00e8me afin de faire persister les actions de rendu. @@ -46,11 +52,15 @@ spaces.savedsearches.name=Recherches enregistr\u00e9es spaces.savedsearches.description=Recherches enregistr\u00e9es spaces.guest_home.name=Espace invit\u00e9 -spaces.guest_home.description=L'espace racine de l'Invit\u00e9 +spaces.guest_home.description=Espace racine de l'invit\u00e9 spaces.scripts.name=Scripts spaces.scripts.description=Fichiers JavaScript +spaces.scripts.example.workflow.name=start-pooled-review-workflow.js +spaces.scripts.example.workflow.title=D\u00e9marrer le workflow R\u00e9viser & Approuver en mode partag\u00e9 +spaces.scripts.example.workflow.description=D\u00e9marrer le workflow R\u00e9viser & Approuver en mode partag\u00e9 pour tous les membres du site auquel le document appartient + spaces.wcm.name=Projets Web spaces.wcm.description=Espaces de gestion de contenu Web @@ -81,8 +91,19 @@ spaces.templates.email.notify.description=Mod\u00e8les d'e-mail de notification spaces.templates.email.generate_the_invite_email.description=Mod\u00e8le d'e-mail utilis\u00e9 pour g\u00e9n\u00e9rer l'e-mail d'invitation pour Alfresco Share email.template.email_template_for_notifying_users=Mod\u00e8le d'e-mail de notification des utilisateurs \u00e0 partir d'une r\u00e8gle ou d'une action + +email.template.email_template_for_notifying_users.sample=Exemple de mod\u00e8le d'e-mail de notification des utilisateurs \u00e0 partir d'une r\u00e8gle ou d'une action email.template.email_template_for_notifying_users_of_an_Invite=Mod\u00e8le d'e-mail de notification des utilisateurs \u00e0 propos d'une invitation \u00e0 un espace ou \u00e0 un document +email.templates.email_template_for_notifying_new_users=Mod\u00e8le d'e-mail utilis\u00e9 pour informer les nouveaux utilisateurs de leur compte + +version.default=Version par d\u00e9faut +version.french=Version fran\u00e7aise +version.german=Version allemande +version.italian=Version italienne +version.japanese=Version japonaise +version.spanish=Version espagnol + spaces.web.client.extension.name=Extension client Web spaces.web.client.extension.title=Client Web personnalis\u00e9 spaces.web.client.extension.description=Client Web personnalis\u00e9 @@ -95,6 +116,31 @@ spaces.messages.name=Messages spaces.messages.title=Messages personnalis\u00e9s spaces.messages.description=Messages personnalis\u00e9s -spaces.workflow.definitions.name=D\u00e9finitions de flux de travail -spaces.workflow.definitions.title=D\u00e9finitions de processus de flux de travail personnalis\u00e9s -spaces.workflow.definitions.description=D\u00e9finitions de processus de flux de travail personnalis\u00e9s +spaces.workflow.definitions.name=D\u00e9finitions de workflow +spaces.workflow.definitions.title=D\u00e9finitions de processus de workflow personnalis\u00e9s +spaces.workflow.definitions.description=D\u00e9finitions de processus de workflow personnalis\u00e9s + +spaces.templates.email.activities.name=activit\u00e9s +spaces.templates.email.activities.description=Mod\u00e8les d'e-mail d'activit\u00e9s + +spaces.templates.email.generate_the_activities_email.description=Mod\u00e8le d'e-mail utilis\u00e9 pour g\u00e9n\u00e9rer l'e-mail d'activit\u00e9s pour Alfresco Share + +spaces.transfers.name=Transferts +spaces.transfers.title=Transferts +spaces.transfers.description=Dossier utilis\u00e9 par le sous-syst\u00e8me de transfert + +spaces.transfer_groups.name=Groupes de cibles de transfert +spaces.transfer_groups.title=Groupes de cibles de transfert +spaces.transfer_groups.description=Dossier contenant des groupes de cibles de transfert + +spaces.transfer_groups_default.name=Groupe par d\u00e9faut +spaces.transfer_groups_default.title=Groupe par d\u00e9faut +spaces.transfer_groups_default.description=Placez vos cibles de transfert dans ce dossier + +spaces.transfer_temp.name=Temp +spaces.transfer_temp.title=Temp +spaces.transfer_temp.description=Dossier de stockage des n\u0153uds temporaires durant le transfert + +spaces.inbound_transfer_records.name=Enregistrements de transfert entrant +spaces.inbound_transfer_records.title=Enregistrements de transfert entrant +spaces.inbound_transfer_records.description=Dossier contenant des enregistrements de transfert entrant diff --git a/config/alfresco/messages/bootstrap-spaces_it.properties b/config/alfresco/messages/bootstrap-spaces_it.properties index 44d478dc8d..6f2d790f79 100755 --- a/config/alfresco/messages/bootstrap-spaces_it.properties +++ b/config/alfresco/messages/bootstrap-spaces_it.properties @@ -12,6 +12,9 @@ spaces.imapConfig.description=Configurazioni IMAP spaces.imap_templates.name=Modelli spaces.imap_templates.description=Modelli per messaggi generati da IMAP +spaces.imap_templates.emailbody_textplain.description=Modello di e-mail utilizzato per generare il corpo del messaggio IMAP "multiparte/alternativo" (parte "testo/normale"). +spaces.imap_templates.emailbody_texthtml.description=Modello di e-mail utilizzato per generare il corpo del messaggio IMAP "multiparte/alternativo" (parte "testo/html"). + spaces.emailActions.name=Azioni di e-mail spaces.emailActions.description=Azioni di e-mail @@ -36,11 +39,14 @@ spaces.notify_templates.email.description=Modelli di notifica tramite e-mail spaces.templates.rss.name=Modelli RSS spaces.templates.rss.description=Modelli RSS +spaces.actions.scheduled_actions.name=Azioni programmate +spaces.actions.scheduled_actions.description=Programma relativo all'esecuzione di azioni persistenti + spaces.rendition.rendering_actions.name=Spazio delle azioni di rendering -spaces.rendition.rendering_actions.description=Spazio utilizzato dal sistema per salvare le azioni di rendering +spaces.rendition.rendering_actions.description=Spazio utilizzato dal sistema per salvare le azioni di rendering. spaces.replication.replication_actions.name=Spazio delle azioni di replica -spaces.replication.replication_actions.description=Spazio utilizzato dal sistema per salvare le azioni di replica +spaces.replication.replication_actions.description=Spazio utilizzato dal sistema per salvare le azioni di replica. spaces.savedsearches.name=Ricerche salvate spaces.savedsearches.description=Ricerche salvate @@ -51,6 +57,10 @@ spaces.guest_home.description=Spazio radice dell'ospite spaces.scripts.name=Script spaces.scripts.description=File JavaScript +spaces.scripts.example.workflow.name=start-pooled-review-workflow.js +spaces.scripts.example.workflow.title=Avvia workflow Esamina e approva in pool +spaces.scripts.example.workflow.description=Avvia il workflow Esamina e approva in pool per tutti i membri del sito a cui appartiene il documento + spaces.wcm.name=Progetti web spaces.wcm.description=Spazi di gestione dei contenuti web @@ -81,8 +91,19 @@ spaces.templates.email.notify.description=Modelli di e-mail di notifica spaces.templates.email.generate_the_invite_email.description=Modello di e-mail utilizzato per generare l'e-mail di invito per Alfresco Share email.template.email_template_for_notifying_users=Modello di e-mail utilizzato per inviare una notifica agli utenti in base a una regola o un'azione + +email.template.email_template_for_notifying_users.sample=Esempio di e-mail utilizzato per inviare una notifica agli utenti in base a una regola o un'azione email.template.email_template_for_notifying_users_of_an_Invite=Modello di e-mail utilizzato per inviare una notifica agli utenti riguardo a un invito a uno spazio o un documento +email.templates.email_template_for_notifying_new_users=Modello di e-mail utilizzato per informare i nuovi utenti dei loro account + +version.default=Versione predefinita +version.french=Versione in lingua francese +version.german=Versione in lingua tedesca +version.italian=Versione in lingua italiana +version.japanese=Versione in lingua giapponese +version.spanish=Versione in lingua spagnola + spaces.web.client.extension.name=Estensione del client web spaces.web.client.extension.title=Client web personalizzato spaces.web.client.extension.description=Client web personalizzato @@ -98,3 +119,28 @@ spaces.messages.description=Messaggi personalizzati spaces.workflow.definitions.name=Definizioni di workflow spaces.workflow.definitions.title=Definizioni del processo di workflow personalizzate spaces.workflow.definitions.description=Definizioni del processo di workflow personalizzate + +spaces.templates.email.activities.name=attivit\u00e0 +spaces.templates.email.activities.description=Modelli di e-mail attivit\u00e0 + +spaces.templates.email.generate_the_activities_email.description=Modello di e-mail utilizzato per generare l'e-mail delle attivit\u00e0 per Alfresco Share + +spaces.transfers.name=Trasferimenti +spaces.transfers.title=Trasferimenti +spaces.transfers.description=Cartella utilizzata dal sottosistema di trasferimento + +spaces.transfer_groups.name=Trasferimento gruppi di destinazione +spaces.transfer_groups.title=Trasferimento gruppi di destinazione +spaces.transfer_groups.description=Cartella contenente i gruppi delle destinazioni di trasferimento + +spaces.transfer_groups_default.name=Gruppo predefinito +spaces.transfer_groups_default.title=Gruppo predefinito +spaces.transfer_groups_default.description=Inserisci destinazioni di trasferimento in questa cartella + +spaces.transfer_temp.name=Temp +spaces.transfer_temp.title=Temp +spaces.transfer_temp.description=Cartella di deposito dei nodi temporanei durante il trasferimento + +spaces.inbound_transfer_records.name=Record di trasferimento in ingresso +spaces.inbound_transfer_records.title=Record di trasferimento in ingresso +spaces.inbound_transfer_records.description=Cartella contenente i record di trasferimento in ingresso diff --git a/config/alfresco/messages/bootstrap-spaces_ja.properties b/config/alfresco/messages/bootstrap-spaces_ja.properties new file mode 100755 index 0000000000..a611ed480e --- /dev/null +++ b/config/alfresco/messages/bootstrap-spaces_ja.properties @@ -0,0 +1,146 @@ +# Labels used in bootstrap Space definitions + +spaces.company_home.name=\u30ab\u30f3\u30d1\u30cb\u30fc\u30db\u30fc\u30e0 +spaces.company_home.description=\u4f1a\u793e\u306e\u30eb\u30fc\u30c8\u30fb\u30b9\u30da\u30fc\u30b9 + +spaces.dictionary.name=\u30c7\u30fc\u30bf\u30fb\u30c7\u30a3\u30af\u30b7\u30e7\u30ca\u30ea +spaces.dictionary.description=\u30e6\u30fc\u30b6\u7ba1\u7406\u306e\u5b9a\u7fa9 + +spaces.imapConfig.name=IMAP\u306e\u69cb\u6210 +spaces.imapConfig.description=IMAP\u306e\u69cb\u6210 + +spaces.imap_templates.name=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +spaces.imap_templates.description=IMAP\u751f\u6210\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +spaces.imap_templates.emailbody_textplain.description=\u300cmultipart/alternative\u300dIMAP\u30e1\u30c3\u30bb\u30fc\u30b8\u672c\u6587\uff08\u300ctext/plain\u300d\u90e8\u5206\uff09\u3092\u751f\u6210\u3059\u308b\u305f\u3081\u306b\u4f7f\u7528\u3059\u308bE\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3002 +spaces.imap_templates.emailbody_texthtml.description=\u300cmultipart/alternative\u300dIMAP\u30e1\u30c3\u30bb\u30fc\u30b8\u672c\u6587\uff08\u300ctext/html\u300d\u90e8\u5206\uff09\u3092\u751f\u6210\u3059\u308b\u305f\u3081\u306b\u4f7f\u7528\u3059\u308bE\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3002 + +spaces.emailActions.name=E\u30e1\u30fc\u30eb\u30fb\u30a2\u30af\u30b7\u30e7\u30f3 +spaces.emailActions.description=E\u30e1\u30fc\u30eb\u30fb\u30a2\u30af\u30b7\u30e7\u30f3 + +spaces.searchAction.name=\u691c\u7d22 +spaces.searchAction.description=\u691c\u7d22 + +spaces.templates.name=\u30b9\u30da\u30fc\u30b9\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +spaces.templates.description=\u30b9\u30da\u30fc\u30b9\u30fb\u30d5\u30a9\u30eb\u30c0\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +spaces.templates.content.name=\u30d7\u30ec\u30bc\u30f3\u30c6\u30fc\u30b7\u30e7\u30f3\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +spaces.templates.content.description=\u30d7\u30ec\u30bc\u30f3\u30c6\u30fc\u30b7\u30e7\u30f3\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +spaces.templates.email.name=E\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +spaces.templates.email.description=E\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +spaces.invite_templates.email.name=E\u30e1\u30fc\u30eb\u62db\u5f85\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +spaces.invite_templates.email.description=E\u30e1\u30fc\u30eb\u62db\u5f85\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +spaces.notify_templates.email.name=E\u30e1\u30fc\u30eb\u901a\u77e5\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +spaces.notify_templates.email.description=E\u30e1\u30fc\u30eb\u901a\u77e5\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +spaces.templates.rss.name=RSS\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +spaces.templates.rss.description=RSS\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +spaces.actions.scheduled_actions.name=\u30b9\u30b1\u30b8\u30e5\u30fc\u30eb\u3057\u305f\u30a2\u30af\u30b7\u30e7\u30f3 +spaces.actions.scheduled_actions.description=\u6c38\u7d9a\u7684\u306a\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u30b9\u30b1\u30b8\u30e5\u30fc\u30eb + +spaces.rendition.rendering_actions.name=\u30ec\u30f3\u30c0\u30ea\u30f3\u30b0\u30fb\u30a2\u30af\u30b7\u30e7\u30f3\u30fb\u30b9\u30da\u30fc\u30b9 +spaces.rendition.rendering_actions.description=\u30ec\u30f3\u30c0\u30ea\u30f3\u30b0\u30fb\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u6c38\u7d9a\u3055\u305b\u308b\u305f\u3081\u306b\u30b7\u30b9\u30c6\u30e0\u304c\u4f7f\u7528\u3059\u308b\u30b9\u30da\u30fc\u30b9\u3002 + +spaces.replication.replication_actions.name=\u30ec\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30fb\u30a2\u30af\u30b7\u30e7\u30f3\u30fb\u30b9\u30da\u30fc\u30b9 +spaces.replication.replication_actions.description=\u30ec\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30fb\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u6c38\u7d9a\u3055\u305b\u308b\u305f\u3081\u306b\u30b7\u30b9\u30c6\u30e0\u304c\u4f7f\u7528\u3059\u308b\u30b9\u30da\u30fc\u30b9\u3002 + +spaces.savedsearches.name=\u4fdd\u5b58\u3057\u305f\u691c\u7d22\u30ad\u30fc\u30ef\u30fc\u30c9 +spaces.savedsearches.description=\u4fdd\u5b58\u3057\u305f\u691c\u7d22\u30ad\u30fc\u30ef\u30fc\u30c9 + +spaces.guest_home.name=\u30b2\u30b9\u30c8\u30db\u30fc\u30e0 +spaces.guest_home.description=\u30b2\u30b9\u30c8\u306e\u30eb\u30fc\u30c8\u30fb\u30b9\u30da\u30fc\u30b9 + +spaces.scripts.name=\u30b9\u30af\u30ea\u30d7\u30c8 +spaces.scripts.description=JavaScript\u30d5\u30a1\u30a4\u30eb + +spaces.scripts.example.workflow.name=start-pooled-review-workflow.js +spaces.scripts.example.workflow.title=\u30d7\u30fc\u30eb\u3055\u308c\u305f\u30ec\u30d3\u30e5\u30fc\u306e\u958b\u59cb\u3068\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u306e\u627f\u8a8d +spaces.scripts.example.workflow.description=\u6587\u66f8\u304c\u5c5e\u3059\u308b\u30b5\u30a4\u30c8\u306e\u5168\u3066\u306e\u30e1\u30f3\u30d0\u306b\u5bfe\u3057\u3066\u3001\u30d7\u30fc\u30eb\u3055\u308c\u305f\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u3092\u958b\u59cb\u3057\u307e\u3059\u3002 + +spaces.wcm.name=Web\u30d7\u30ed\u30b8\u30a7\u30af\u30c8 +spaces.wcm.description=Web\u30b3\u30f3\u30c6\u30f3\u30c4\u7ba1\u7406\u30b9\u30da\u30fc\u30b9 + +spaces.wcm_content_forms.name=Web\u30d5\u30a9\u30fc\u30e0 +spaces.wcm_content_forms.description=Web\u30b3\u30f3\u30c6\u30f3\u30c4\u30fb\u30d5\u30a9\u30fc\u30e0 + +spaces.user_homes.name=\u30e6\u30fc\u30b6\u30fb\u30db\u30fc\u30e0 +spaces.user_homes.description=\u30e6\u30fc\u30b6\u30fb\u30db\u30fc\u30e0 + +spaces.content_forms.name=\u30d5\u30a9\u30fc\u30e0 +spaces.content_forms.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u30fb\u30d5\u30a9\u30fc\u30e0 + +spaces.sites.name=\u30b5\u30a4\u30c8 +spaces.sites.description=\u30b5\u30a4\u30c8\u30fb\u30b3\u30e9\u30dc\u30ec\u30fc\u30b7\u30e7\u30f3\u30fb\u30b9\u30da\u30fc\u30b9 + +spaces.templates.email.invite.name=\u62db\u5f85\u3059\u308b +spaces.templates.email.invite.description=E\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u62db\u5f85\u3059\u308b + +spaces.imap_home.name=IMAP\u30db\u30fc\u30e0 +spaces.imap_home.description=IMAP\u30db\u30fc\u30e0 + +spaces.templates.email.invite1.name=E\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u62db\u5f85\u3059\u308b +spaces.templates.email.invite1.description=E\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u62db\u5f85\u3059\u308b + +spaces.templates.email.notify.name=E\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u901a\u77e5\u3059\u308b +spaces.templates.email.notify.description=E\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u3092\u901a\u77e5\u3059\u308b + +spaces.templates.email.generate_the_invite_email.description=Alfresco Share\u306e\u62db\u5f85E\u30e1\u30fc\u30eb\u3092\u751f\u6210\u3059\u308b\u305f\u3081\u306b\u4f7f\u7528\u3059\u308bE\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +email.template.email_template_for_notifying_users=\u30eb\u30fc\u30eb\u307e\u305f\u306f\u30a2\u30af\u30b7\u30e7\u30f3\u304b\u3089\u30e6\u30fc\u30b6\u306b\u901a\u77e5\u3059\u308b\u305f\u3081\u306eE\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +email.template.email_template_for_notifying_users.sample=\u30eb\u30fc\u30eb\u307e\u305f\u306f\u30a2\u30af\u30b7\u30e7\u30f3\u304b\u3089\u30e6\u30fc\u30b6\u306b\u901a\u77e5\u3059\u308b\u305f\u3081\u306e\u30b5\u30f3\u30d7\u30ebE\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +email.template.email_template_for_notifying_users_of_an_Invite=\u30b9\u30da\u30fc\u30b9\u307e\u305f\u306f\u6587\u66f8\u3078\u306e\u62db\u5f85\u3092\u30e6\u30fc\u30b6\u306b\u901a\u77e5\u3059\u308b\u305f\u3081\u306eE\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +email.templates.email_template_for_notifying_new_users=\u65b0\u3057\u3044\u30e6\u30fc\u30b6\u306b\u30a2\u30ab\u30a6\u30f3\u30c8\u306b\u3064\u3044\u3066\u901a\u77e5\u3059\u308b\u305f\u3081\u306b\u4f7f\u7528\u3059\u308bE\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +version.default=\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u30d0\u30fc\u30b8\u30e7\u30f3 +version.french=\u30d5\u30e9\u30f3\u30b9\u8a9e\u30d0\u30fc\u30b8\u30e7\u30f3 +version.german=\u30c9\u30a4\u30c4\u8a9e\u30d0\u30fc\u30b8\u30e7\u30f3 +version.italian=\u30a4\u30bf\u30ea\u30a2\u8a9e\u30d0\u30fc\u30b8\u30e7\u30f3 +version.japanese=\u65e5\u672c\u8a9e\u30d0\u30fc\u30b8\u30e7\u30f3 +version.spanish=\u30b9\u30da\u30a4\u30f3\u8a9e\u30d0\u30fc\u30b8\u30e7\u30f3 + +spaces.web.client.extension.name=Web\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u306e\u62e1\u5f35 +spaces.web.client.extension.title=\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3055\u308c\u305fWeb\u30af\u30e9\u30a4\u30a2\u30f3\u30c8 +spaces.web.client.extension.description=\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3055\u308c\u305fWeb\u30af\u30e9\u30a4\u30a2\u30f3\u30c8 + +spaces.models.name=\u30e2\u30c7\u30eb +spaces.models.title=\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3055\u308c\u305f\u30e2\u30c7\u30eb +spaces.models.description=\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3055\u308c\u305f\u30e2\u30c7\u30eb + +spaces.messages.name=\u30e1\u30c3\u30bb\u30fc\u30b8 +spaces.messages.title=\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3055\u308c\u305f\u30e1\u30c3\u30bb\u30fc\u30b8 +spaces.messages.description=\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3055\u308c\u305f\u30e1\u30c3\u30bb\u30fc\u30b8 + +spaces.workflow.definitions.name=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u306e\u5b9a\u7fa9 +spaces.workflow.definitions.title=\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3055\u308c\u305f\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30d7\u30ed\u30bb\u30b9\u306e\u5b9a\u7fa9 +spaces.workflow.definitions.description=\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3055\u308c\u305f\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30d7\u30ed\u30bb\u30b9\u306e\u5b9a\u7fa9 + +spaces.templates.email.activities.name=\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3 +spaces.templates.email.activities.description=\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3\u306eE\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +spaces.templates.email.generate_the_activities_email.description=Alfresco Share\u306e\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3E\u30e1\u30fc\u30eb\u3092\u751f\u6210\u3059\u308b\u305f\u3081\u306b\u4f7f\u7528\u3059\u308bE\u30e1\u30fc\u30eb\u30fb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +spaces.transfers.name=\u8ee2\u9001 +spaces.transfers.title=\u8ee2\u9001 +spaces.transfers.description=\u8ee2\u9001\u30b5\u30d6\u30b7\u30b9\u30c6\u30e0\u306b\u3088\u3063\u3066\u4f7f\u7528\u3055\u308c\u308b\u30d5\u30a9\u30eb\u30c0 + +spaces.transfer_groups.name=\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u30fb\u30b0\u30eb\u30fc\u30d7 +spaces.transfer_groups.title=\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u30fb\u30b0\u30eb\u30fc\u30d7 +spaces.transfer_groups.description=\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u30b0\u30eb\u30fc\u30d7\u3092\u542b\u3080\u30d5\u30a9\u30eb\u30c0 + +spaces.transfer_groups_default.name=\u30c7\u30d5\u30a9\u30eb\u30c8\u30fb\u30b0\u30eb\u30fc\u30d7 +spaces.transfer_groups_default.title=\u30c7\u30d5\u30a9\u30eb\u30c8\u30fb\u30b0\u30eb\u30fc\u30d7 +spaces.transfer_groups_default.description=\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u306b\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u3092\u5165\u308c\u308b + +spaces.transfer_temp.name=\u4e00\u6642 +spaces.transfer_temp.title=\u4e00\u6642 +spaces.transfer_temp.description=\u8ee2\u9001\u4e2d\u306b\u4e00\u6642\u30ce\u30fc\u30c9\u3092\u683c\u7d0d\u3059\u308b\u30d5\u30a9\u30eb\u30c0 + +spaces.inbound_transfer_records.name=\u30a4\u30f3\u30d0\u30a6\u30f3\u30c9\u8ee2\u9001\u30ec\u30b3\u30fc\u30c9 +spaces.inbound_transfer_records.title=\u30a4\u30f3\u30d0\u30a6\u30f3\u30c9\u8ee2\u9001\u30ec\u30b3\u30fc\u30c9 +spaces.inbound_transfer_records.description=\u30a4\u30f3\u30d0\u30a6\u30f3\u30c9\u8ee2\u9001\u306e\u30ec\u30b3\u30fc\u30c9\u3092\u542b\u3080\u30d5\u30a9\u30eb\u30c0 diff --git a/config/alfresco/messages/bootstrap-templates_ja.properties b/config/alfresco/messages/bootstrap-templates_ja.properties new file mode 100755 index 0000000000..0c0aae7def --- /dev/null +++ b/config/alfresco/messages/bootstrap-templates_ja.properties @@ -0,0 +1,14 @@ +# Labels used in bootstrap Template definitions + +templates.space.project=\u30bd\u30d5\u30c8\u30a6\u30a7\u30a2\u30fb\u30a8\u30f3\u30b8\u30cb\u30a2\u30ea\u30f3\u30b0\u30fb\u30d7\u30ed\u30b8\u30a7\u30af\u30c8 +templates.space.documentation=\u6587\u66f8\u5316 +templates.space.drafts=\u30c9\u30e9\u30d5\u30c8 +templates.space.pending_approval=\u672a\u6c7a\u5b9a\u306e\u627f\u8a8d +templates.space.published=\u516c\u958b\u6e08 +templates.space.samples=\u30b5\u30f3\u30d7\u30eb +templates.document.system_overview.title=\u30b7\u30b9\u30c6\u30e0\u6982\u8981 +templates.document.system_overview.name=system-overview.html +templates.space.discussions=\u30c7\u30a3\u30b9\u30ab\u30c3\u30b7\u30e7\u30f3 +templates.space.ui_design=UI\u30c7\u30b6\u30a4\u30f3 +templates.space.presentations=\u30d7\u30ec\u30bc\u30f3\u30c6\u30fc\u30b7\u30e7\u30f3 +templates.space.quality_assurance=\u54c1\u8cea\u4fdd\u8a3c diff --git a/config/alfresco/messages/bootstrap-tutorial_ja.properties b/config/alfresco/messages/bootstrap-tutorial_ja.properties new file mode 100755 index 0000000000..fce49b01a7 --- /dev/null +++ b/config/alfresco/messages/bootstrap-tutorial_ja.properties @@ -0,0 +1,9 @@ +# Labels used in bootstrap Tutorial definitions + +tutorial.space.name=Alfresco Tutorial +tutorial.space.description=Alfresco\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30b9\u30c6\u30c3\u30d7\u30fb\u30d0\u30a4\u30fb\u30b9\u30c6\u30c3\u30d7\u30ac\u30a4\u30c9 + +tutorial.document.name=Alfresco-Tutorial.pdf +tutorial.document.title=Alfresco Tutorial +tutorial.document.description=\u30b9\u30bf\u30fc\u30c8\u30ac\u30a4\u30c9 + diff --git a/config/alfresco/messages/bootstrap-webScriptsExtensions_ja.properties b/config/alfresco/messages/bootstrap-webScriptsExtensions_ja.properties new file mode 100755 index 0000000000..e94d9973cf --- /dev/null +++ b/config/alfresco/messages/bootstrap-webScriptsExtensions_ja.properties @@ -0,0 +1,5 @@ +webscriptsextentions.customized_web_scripts=\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3055\u308c\u305fWeb\u30fb\u30b9\u30af\u30ea\u30d7\u30c8 +webscriptsextentions.url_addressable_web_service_extensions=URL\u30a2\u30c9\u30ec\u30b9\u53ef\u80fd\u306aWeb\u30fb\u30b5\u30fc\u30d3\u30b9\u62e1\u5f35\u5b50 +webscriptsextentions.web_scripts_extensions=Web\u30fb\u30b9\u30af\u30ea\u30d7\u30c8\u62e1\u5f35\u5b50 +webscriptsextentions.how_to_customize_an_existing_web_script=\u65e2\u5b58\u306eWeb\u30fb\u30b9\u30af\u30ea\u30d7\u30c8\u306e\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u65b9\u6cd5 + diff --git a/config/alfresco/messages/bootstrap-webScripts_it.properties b/config/alfresco/messages/bootstrap-webScripts_it.properties index d4ec718430..fc39e9162d 100755 --- a/config/alfresco/messages/bootstrap-webScripts_it.properties +++ b/config/alfresco/messages/bootstrap-webScripts_it.properties @@ -1,3 +1,3 @@ webscripts.url_addressable_web_services=Servizi web identificabili con indirizzi URL webscripts.web_scripts=Web Script -webscripts.what_are_web_scripts=Cosa sono i Web Script e come vengono sviluppati +webscripts.what_are_web_scripts=Cosa sono i Web Script e come vengono sviluppati? diff --git a/config/alfresco/messages/bootstrap-webScripts_ja.properties b/config/alfresco/messages/bootstrap-webScripts_ja.properties new file mode 100755 index 0000000000..1514bc9bca --- /dev/null +++ b/config/alfresco/messages/bootstrap-webScripts_ja.properties @@ -0,0 +1,4 @@ +webscripts.url_addressable_web_services=URL\u30a2\u30c9\u30ec\u30b9\u53ef\u80fd\u306aWeb\u30fb\u30b5\u30fc\u30d3\u30b9 +webscripts.web_scripts=Web\u30fb\u30b9\u30af\u30ea\u30d7\u30c8 +webscripts.what_are_web_scripts=Web\u30fb\u30b9\u30af\u30ea\u30d7\u30c8\u3068\u306f\u3001\u307e\u305f\u305d\u308c\u3092\u958b\u767a\u3059\u308b\u65b9\u6cd5\u306f? + diff --git a/config/alfresco/messages/bpm-messages_fr.properties b/config/alfresco/messages/bpm-messages_fr.properties index d8d7a478be..bb09d2569f 100755 --- a/config/alfresco/messages/bpm-messages_fr.properties +++ b/config/alfresco/messages/bpm-messages_fr.properties @@ -43,14 +43,14 @@ bpm_businessprocessmodel.property.bpm_description.description=Description de ce bpm_businessprocessmodel.property.bpm_outcome.title=R\u00e9sultat bpm_businessprocessmodel.property.bpm_outcome.description=D\u00e9cision sur l'ach\u00e8vement de la T\u00e2che bpm_businessprocessmodel.property.bpm_completedItems.title=El\u00e9ments Achev\u00e9s -bpm_businessprocessmodel.property.bpm_completedItems.description=Package d'\u00e9l\u00e9ments marqu\u00e9s comme achev\u00e9s +bpm_businessprocessmodel.property.bpm_completedItems.description=Paquetag d'\u00e9l\u00e9ments marqu\u00e9s comme achev\u00e9s bpm_businessprocessmodel.property.bpm_packageActionGroup.title=Paquetage des actions bpm_businessprocessmodel.property.bpm_packageActionGroup.description=Actions disponibles sur le paquetage du workflow bpm_businessprocessmodel.property.bpm_packageItemActionGroup.title=Actions des \u00e9l\u00e9ments du paquetage bpm_businessprocessmodel.property.bpm_packageItemActionGroup.description=Actions disponibles sur les \u00e9l\u00e9ments du paquetage du workflow bpm_businessprocessmodel.association.bpm_package.title=Paquetage du Contenu bpm_businessprocessmodel.association.bpm_package.description=La collection du contenu rout\u00e9 par le workflow -bpm_businessprocessmodel.aspect.bpm_workflowPackage.title=Package du Workflow +bpm_businessprocessmodel.aspect.bpm_workflowPackage.title=Paquetage du Workflow bpm_businessprocessmodel.aspect.bpm_workflowPackage.description=La collection du contenu rout\u00e9 par le workflow # Workflow Start Task @@ -58,8 +58,8 @@ bpm_businessprocessmodel.type.bpm_startTask.title=T\u00e2che de D\u00e9marrage d bpm_businessprocessmodel.type.bpm_startTask.description=T\u00e2che utilis\u00e9e pour collecter des informations n\u00e9cessaires pour d\u00e9marrer le Workflow bpm_businessprocessmodel.property.bpm_workflowDescription.title=Description bpm_businessprocessmodel.property.bpm_workflowDescription.description=Description -bpm_businessprocessmodel.property.bpm_workflowDueDate.title=Ech\u00e9ance du workflow -bpm_businessprocessmodel.property.bpm_workflowDueDate.description=Ech\u00e9ance du workflow +bpm_businessprocessmodel.property.bpm_workflowDueDate.title=Ech\u00e9ance du Workflow +bpm_businessprocessmodel.property.bpm_workflowDueDate.description=Ech\u00e9ance du Workflow bpm_businessprocessmodel.property.bpm_workflowPriority.title=Priorit\u00e9 du Workflow bpm_businessprocessmodel.property.bpm_workflowPriority.description=Priorit\u00e9 du Workflow bpm_businessprocessmodel.association.bpm_assignee.title=Propri\u00e9taire du Workflow diff --git a/config/alfresco/messages/bpm-messages_ja.properties b/config/alfresco/messages/bpm-messages_ja.properties new file mode 100755 index 0000000000..4472db1c33 --- /dev/null +++ b/config/alfresco/messages/bpm-messages_ja.properties @@ -0,0 +1,72 @@ +# Display labels for base Business Process Model + +bpm_businessprocessmodel.title=\u30d3\u30b8\u30cd\u30b9\u30fb\u30d7\u30ed\u30bb\u30b9\u30fb\u30e2\u30c7\u30eb +bpm_businessprocessmodel.description=\u5168\u3066\u306e\u30d3\u30b8\u30cd\u30b9\u30fb\u30d7\u30ed\u30bb\u30b9\u306e\u57fa\u790e\u5b9a\u7fa9 + +# Default transition +bpm_businessprocessmodel.transition.title=\u30bf\u30b9\u30af\u5b8c\u4e86 +bpm_businessprocessmodel.transition.description=\u30bf\u30b9\u30af\u5b8c\u4e86 + +# Base Task +bpm_businessprocessmodel.type.bpm_task.title=\u30bf\u30b9\u30af +bpm_businessprocessmodel.type.bpm_task.description=\u30bf\u30b9\u30af +bpm_businessprocessmodel.property.bpm_taskId.title=ID +bpm_businessprocessmodel.property.bpm_taskId.description=ID +bpm_businessprocessmodel.property.bpm_startDate.title=\u958b\u59cb\u65e5 +bpm_businessprocessmodel.property.bpm_startDate.description=\u958b\u59cb\u65e5 +bpm_businessprocessmodel.property.bpm_completionDate.title=\u5b8c\u4e86\u65e5 +bpm_businessprocessmodel.property.bpm_completionDate.description=\u5b8c\u4e86\u65e5 +bpm_businessprocessmodel.property.bpm_dueDate.title=\u7de0\u5207\u65e5 +bpm_businessprocessmodel.property.bpm_dueDate.description=\u7de0\u5207\u65e5 +bpm_businessprocessmodel.property.bpm_status.title=\u30b9\u30c6\u30fc\u30bf\u30b9 +bpm_businessprocessmodel.property.bpm_status.description=\u30b9\u30c6\u30fc\u30bf\u30b9 +bpm_businessprocessmodel.property.bpm_priority.title=\u512a\u5148 +bpm_businessprocessmodel.property.bpm_priority.description=\u512a\u5148 +bpm_businessprocessmodel.property.bpm_percentComplete.title=\u9032\u6357\u5ea6 +bpm_businessprocessmodel.property.bpm_percentComplete.description=\u9032\u6357\u5ea6 +bpm_businessprocessmodel.property.bpm_comment.title=\u30b3\u30e1\u30f3\u30c8 +bpm_businessprocessmodel.property.bpm_comment.description=\u30b3\u30e1\u30f3\u30c8 +bpm_businessprocessmodel.association.bpm_pooledActors.title=\u30d7\u30fc\u30eb\u3055\u308c\u305f\u30e6\u30fc\u30b6\u30fc +bpm_businessprocessmodel.association.bpm_pooledActors.description=\u30d7\u30fc\u30eb + +# Workflow Task +bpm_businessprocessmodel.type.bpm_workflowTask.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30bf\u30b9\u30af +bpm_businessprocessmodel.type.bpm_workflowTask.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u306b\u3088\u3063\u3066\u5272\u308a\u5f53\u3066\u3089\u308c\u305f\u30bf\u30b9\u30af +bpm_businessprocessmodel.property.bpm_workflowDefinitionId.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u5b9a\u7fa9ID +bpm_businessprocessmodel.property.bpm_workflowDefinitionId.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u5b9a\u7fa9ID +bpm_businessprocessmodel.property.bpm_workflowInstanceId.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9ID +bpm_businessprocessmodel.property.bpm_workflowInstanceId.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9ID +bpm_businessprocessmodel.property.bpm_context.title=\u30bf\u30b9\u30af\u30fb\u30b3\u30f3\u30c6\u30af\u30b9\u30c8 +bpm_businessprocessmodel.property.bpm_context.description=\u3053\u306e\u30bf\u30b9\u30af\u304c\u5272\u308a\u5f53\u3066\u3089\u308c\u3066\u3044\u308b\u30b3\u30f3\u30c6\u30af\u30b9\u30c8 +bpm_businessprocessmodel.property.bpm_description.title=\u8aac\u660e +bpm_businessprocessmodel.property.bpm_description.description=\u3053\u306e\u30bf\u30b9\u30af\u306e\u76ee\u7684\u306e\u8aac\u660e +bpm_businessprocessmodel.property.bpm_outcome.title=\u7d50\u679c +bpm_businessprocessmodel.property.bpm_outcome.description=\u30bf\u30b9\u30af\u5b8c\u4e86\u6642\u306b\u4e0b\u3055\u308c\u305f\u5224\u65ad +bpm_businessprocessmodel.property.bpm_completedItems.title=\u5b8c\u4e86\u30a2\u30a4\u30c6\u30e0 +bpm_businessprocessmodel.property.bpm_completedItems.description=\u5b8c\u4e86\u6e08\u3068\u30de\u30fc\u30af\u3055\u308c\u305f\u30d1\u30c3\u30b1\u30fc\u30b8\u30a2\u30a4\u30c6\u30e0 +bpm_businessprocessmodel.property.bpm_packageActionGroup.title=\u30d1\u30c3\u30b1\u30fc\u30b8\u30fb\u30a2\u30af\u30b7\u30e7\u30f3 +bpm_businessprocessmodel.property.bpm_packageActionGroup.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30d1\u30c3\u30b1\u30fc\u30b8\u4e0a\u3067\u5229\u7528\u53ef\u80fd\u306a\u30a2\u30af\u30b7\u30e7\u30f3 +bpm_businessprocessmodel.property.bpm_packageItemActionGroup.title=\u30d1\u30c3\u30b1\u30fc\u30b8\u30fb\u30a2\u30a4\u30c6\u30e0\u30fb\u30a2\u30af\u30b7\u30e7\u30f3 +bpm_businessprocessmodel.property.bpm_packageItemActionGroup.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30d1\u30c3\u30b1\u30fc\u30b8\u30fb\u30a2\u30a4\u30c6\u30e0\u3067\u5229\u7528\u53ef\u80fd\u306a\u30a2\u30af\u30b7\u30e7\u30f3 +bpm_businessprocessmodel.association.bpm_package.title=\u30b3\u30f3\u30c6\u30f3\u30c4\u30fb\u30d1\u30c3\u30b1\u30fc\u30b8 +bpm_businessprocessmodel.association.bpm_package.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u3092\u901a\u3058\u3066\u9001\u3089\u308c\u308b\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u96c6\u307e\u308a +bpm_businessprocessmodel.aspect.bpm_workflowPackage.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30d1\u30c3\u30b1\u30fc\u30b8 +bpm_businessprocessmodel.aspect.bpm_workflowPackage.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u3092\u901a\u3058\u3066\u9001\u3089\u308c\u308b\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u96c6\u307e\u308a + +# Workflow Start Task +bpm_businessprocessmodel.type.bpm_startTask.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u958b\u59cb\u30bf\u30b9\u30af +bpm_businessprocessmodel.type.bpm_startTask.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u306e\u958b\u59cb\u306b\u5fc5\u8981\u306a\u60c5\u5831\u53ce\u96c6\u306b\u4f7f\u7528\u3055\u308c\u308b\u30bf\u30b9\u30af +bpm_businessprocessmodel.property.bpm_workflowDescription.title=\u8aac\u660e +bpm_businessprocessmodel.property.bpm_workflowDescription.description=\u8aac\u660e +bpm_businessprocessmodel.property.bpm_workflowDueDate.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u7de0\u5207\u65e5 +bpm_businessprocessmodel.property.bpm_workflowDueDate.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u7de0\u5207\u65e5 +bpm_businessprocessmodel.property.bpm_workflowPriority.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u512a\u5148 +bpm_businessprocessmodel.property.bpm_workflowPriority.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u512a\u5148 +bpm_businessprocessmodel.association.bpm_assignee.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u62c5\u5f53\u8005 +bpm_businessprocessmodel.association.bpm_assignee.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u62c5\u5f53\u8005 +bpm_businessprocessmodel.association.bpm_assignees.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u62c5\u5f53\u8005 +bpm_businessprocessmodel.association.bpm_assignees.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u62c5\u5f53\u8005 +bpm_businessprocessmodel.association.bpm_groupAssignee.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30b0\u30eb\u30fc\u30d7\u62c5\u5f53\u8005 +bpm_businessprocessmodel.association.bpm_groupAssignee.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30b0\u30eb\u30fc\u30d7\u62c5\u5f53\u8005 +bpm_businessprocessmodel.association.bpm_groupAssignees.title=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30b0\u30eb\u30fc\u30d7\u62c5\u5f53\u8005 +bpm_businessprocessmodel.association.bpm_groupAssignees.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30fb\u30b0\u30eb\u30fc\u30d7\u62c5\u5f53\u8005 diff --git a/config/alfresco/messages/coci-service_fr.properties b/config/alfresco/messages/coci-service_fr.properties index 20966e22ea..fdb3303423 100755 --- a/config/alfresco/messages/coci-service_fr.properties +++ b/config/alfresco/messages/coci-service_fr.properties @@ -1,7 +1,7 @@ # coci service externalised display strings coci_service.working_copy_label=(Copie de Travail) -coci_service.err_bad_copy=Le n\u009cud original est introuvable. La copie a peut-\u00eatre \u00e9t\u00e9 corrompue ou l'original a \u00e9t\u00e9 supprim\u00e9 ou d\u00e9plac\u00e9. +coci_service.err_bad_copy=Le n\u009cud original est introuvable. La copie a peut-\u00eatre \u00e9t\u00e9 corrompue ou l'original a \u00e9t\u00e9 supprim\u00e9 ou d\u00e9plac\u00e9. coci_service.err_not_owner=L'utilisateur n'est pas le propri\u00e9taire de la copie de travail et ne peut pas lib\u00e9rer celle-ci. coci_service.err_workingcopy_checkout=Vous ne pouvez pas r\u00e9server une copie de travail. coci_service.err_not_authenticated=Les d\u00e9tails de l'utilisateur actuellement authentifi\u00e9, requis par le service CheckOutCheckIn, sont introuvables. diff --git a/config/alfresco/messages/coci-service_ja.properties b/config/alfresco/messages/coci-service_ja.properties new file mode 100755 index 0000000000..7ecfd80cdb --- /dev/null +++ b/config/alfresco/messages/coci-service_ja.properties @@ -0,0 +1,11 @@ +# coci service externalised display strings + +coci_service.working_copy_label=(\u4f5c\u696d\u7528\u30b3\u30d4\u30fc) +coci_service.err_bad_copy=\u5143\u306e\u30ce\u30fc\u30c9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u30b3\u30d4\u30fc\u304c\u58ca\u308c\u3066\u3044\u308b\u304b\u3001\u5143\u306e\u30ce\u30fc\u30c9\u304c\u524a\u9664\u307e\u305f\u306f\u79fb\u52d5\u3055\u308c\u305f\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002 +coci_service.err_not_owner=\u3053\u306e\u30e6\u30fc\u30b6\u30fc\u306f\u4f5c\u696d\u30b3\u30d4\u30fc\u306e\u30aa\u30fc\u30ca\u30fc\u3067\u306a\u3044\u305f\u3081\u3001\u30c1\u30a7\u30c3\u30af\u30a4\u30f3\u3067\u304d\u307e\u305b\u3093\u3002 +coci_service.err_workingcopy_checkout=\u4f5c\u696d\u30b3\u30d4\u30fc\u306f\u30c1\u30a7\u30c3\u30af\u30a2\u30a6\u30c8\u3067\u304d\u307e\u305b\u3093\u3002 +coci_service.err_not_authenticated=CheckOutCheckIn\u30b5\u30fc\u30d3\u30b9\u304c\u8981\u6c42\u3059\u308b\u3001\u73fe\u5728\u306e\u8a8d\u8a3c\u6e08\u307f\u30e6\u30fc\u30b6\u30fc\u60c5\u5831\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 +coci_service.err_workingcopy_has_no_mimetype=\u4f5c\u696d\u30b3\u30d4\u30fc\u30ce\u30fc\u30c9({0})\u306bMIME\u30bf\u30a4\u30d7\u304c\u3042\u308a\u307e\u305b\u3093 +coci_service.err_already_checkedout=\u3053\u306e\u30ce\u30fc\u30c9\u306f\u3059\u3067\u306b\u30c1\u30a7\u30c3\u30af\u30a2\u30a6\u30c8\u3055\u308c\u3066\u3044\u307e\u3059\u3002 +coci_service.err_cannot_rename={0} \u304b\u3089 {1} \u306b\u540d\u524d\u3092\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\u3002 +coci_service.discussion_for={0} \u30c7\u30a3\u30b9\u30ab\u30c3\u30b7\u30e7\u30f3 diff --git a/config/alfresco/messages/content-filter-languages_ja.properties b/config/alfresco/messages/content-filter-languages_ja.properties new file mode 100755 index 0000000000..6e1b6f28ab --- /dev/null +++ b/config/alfresco/messages/content-filter-languages_ja.properties @@ -0,0 +1,194 @@ +## Translations of ISO 639-1 languages codes + +content_filter_lang.aa=\u30a2\u30d5\u30a1\u30eb\u8a9e +content_filter_lang.ab=\u30a2\u30d6\u30cf\u30ba\u8a9e +content_filter_lang.ae=\u30a2\u30f4\u30a7\u30b9\u30bf\u30fc\u8a9e +content_filter_lang.af=\u30a2\u30d5\u30ea\u30ab\u30fc\u30f3\u30b9\u8a9e +content_filter_lang.ak=\u30a2\u30ab\u30f3\u8a9e +content_filter_lang.am=\u30a2\u30e0\u30cf\u30e9\u8a9e +content_filter_lang.an=\u30a2\u30e9\u30b4\u30f3\u65b9\u8a00 +content_filter_lang.ar=\u30a2\u30e9\u30d3\u30a2\u8a9e +content_filter_lang.as=\u30a2\u30c3\u30b5\u30e0\u8a9e +content_filter_lang.av=\u30a2\u30f4\u30a1\u30ea\u30af\u8a9e +content_filter_lang.ay=\u30a2\u30a4\u30de\u30e9\u8a9e +content_filter_lang.az=\u30a2\u30bc\u30eb\u30d0\u30a4\u30b8\u30e3\u30f3\u8a9e +content_filter_lang.ba=\u30d0\u30b7\u30ad\u30fc\u30eb\u8a9e +content_filter_lang.be=\u30d9\u30e9\u30eb\u30fc\u30b7\u8a9e +content_filter_lang.bg=\u30d6\u30eb\u30ac\u30ea\u30a2\u8a9e +content_filter_lang.bh=\u30d3\u30cf\u30fc\u30eb\u8a9e +content_filter_lang.bi=\u30d3\u30b9\u30e9\u30de\u8a9e +content_filter_lang.bm=\u30d0\u30f3\u30d0\u30e9\u8a9e +content_filter_lang.bn=\u30d9\u30f3\u30ac\u30eb\u8a9e +content_filter_lang.bo=\u30c1\u30d9\u30c3\u30c8\u8a9e +content_filter_lang.br=\u30d6\u30eb\u30c8\u30f3\u8a9e +content_filter_lang.bs=\u30dc\u30b9\u30cb\u30a2\u8a9e +content_filter_lang.ca=\u30ab\u30bf\u30ed\u30cb\u30a2\u8a9e\u3001\u30d0\u30ec\u30f3\u30b7\u30a2\u8a9e +content_filter_lang.ce=\u30c1\u30a7\u30c1\u30a7\u30f3\u8a9e +content_filter_lang.ch=\u30c1\u30e3\u30e2\u30ed\u8a9e +content_filter_lang.co=\u30b3\u30eb\u30b7\u30ab\u65b9\u8a00 +content_filter_lang.cr=\u30af\u30ea\u30fc\u8a9e +content_filter_lang.cs=\u30c1\u30a7\u30b3\u8a9e +content_filter_lang.cu=\u30b9\u30e9\u30d6\u8a9e +content_filter_lang.cv=\u30c1\u30e5\u30d0\u30b7\u8a9e +content_filter_lang.cy=\u30a6\u30a7\u30fc\u30eb\u30ba\u8a9e +content_filter_lang.da=\u30c7\u30f3\u30de\u30fc\u30af\u8a9e +content_filter_lang.de=\u30c9\u30a4\u30c4\u8a9e +content_filter_lang.dv=\u30e2\u30eb\u30b8\u30d6\u8a9e +content_filter_lang.dz=\u30be\u30f3\u30ab\u8a9e +content_filter_lang.ee=\u30a8\u30a6\u30a7\u8a9e +content_filter_lang.el=\u30ae\u30ea\u30b7\u30e3\u8a9e +content_filter_lang.en=\u82f1\u8a9e +content_filter_lang.eo=\u30a8\u30b9\u30da\u30e9\u30f3\u30c8\u8a9e +content_filter_lang.es=\u30b9\u30da\u30a4\u30f3\u8a9e +content_filter_lang.et=\u30a8\u30b9\u30c8\u30cb\u30a2\u8a9e +content_filter_lang.eu=\u30d0\u30b9\u30af\u8a9e +content_filter_lang.fa=\u30da\u30eb\u30b7\u30e3\u8a9e +content_filter_lang.ff=\u30d5\u30e9\u8a9e +content_filter_lang.fi=\u30d5\u30a3\u30f3\u30e9\u30f3\u30c9\u8a9e +content_filter_lang.fj=\u30d5\u30a3\u30fc\u30b8\u30fc\u8a9e +content_filter_lang.fo=\u30d5\u30a7\u30ed\u30fc\u8a9e +content_filter_lang.fr=\u30d5\u30e9\u30f3\u30b9\u8a9e +content_filter_lang.fy=\u897f\u30d5\u30ea\u30b8\u30a2\u8a9e +content_filter_lang.ga=\u30a2\u30a4\u30eb\u30e9\u30f3\u30c9\u8a9e +content_filter_lang.gd=\u30b2\u30fc\u30eb\u8a9e +content_filter_lang.gl=\u30ac\u30ea\u30b7\u30a2\u8a9e +content_filter_lang.gn=\u30b0\u30a2\u30e9\u30cb\u8a9e +content_filter_lang.gu=\u30b0\u30b8\u30e3\u30e9\u30fc\u30c8\u8a9e +content_filter_lang.gv=\u30de\u30f3\u5cf6\u8a9e +content_filter_lang.ha=\u30cf\u30a6\u30b5\u8a9e +content_filter_lang.he=\u30d8\u30d6\u30e9\u30a4\u8a9e +content_filter_lang.hi=\u30d2\u30f3\u30c7\u30a3\u30fc\u8a9e +content_filter_lang.ho=\u30d2\u30ea\u30fb\u30e2\u30c4\u8a9e +content_filter_lang.hr=\u30af\u30ed\u30a2\u30c1\u30a2\u8a9e +content_filter_lang.ht=\u30cf\u30a4\u30c1\u8a9e +content_filter_lang.hu=\u30cf\u30f3\u30ac\u30ea\u30fc\u8a9e +content_filter_lang.hy=\u30a2\u30eb\u30e1\u30cb\u30a2\u8a9e +content_filter_lang.hz=\u30d8\u30ec\u30ed\u8a9e +content_filter_lang.ia=\u30a4\u30f3\u30bf\u30fc\u30ea\u30f3\u30b0\u30a2 +content_filter_lang.id=\u30a4\u30f3\u30c9\u30cd\u30b7\u30a2\u8a9e +content_filter_lang.ie=\u30a4\u30f3\u30bf\u30fc\u30ea\u30f3\u30b0 +content_filter_lang.ig=\u30a4\u30dc\u8a9e +content_filter_lang.ii=\u30b7\u30c1\u30e5\u30a2\u30f3\u30fb\u30a4\u8a9e +content_filter_lang.ik=\u30a4\u30cc\u30d4\u30a2\u30c3\u30af\u8a9e +content_filter_lang.io=\u30a4\u30c9\u8a9e +content_filter_lang.is=\u30a2\u30a4\u30b9\u30e9\u30f3\u30c9\u8a9e +content_filter_lang.it=\u30a4\u30bf\u30ea\u30a2\u8a9e +content_filter_lang.iu=\u30a4\u30cc\u30af\u30c6\u30a3\u30c8\u30a5\u30c8\u8a9e +content_filter_lang.ja=\u65e5\u672c\u8a9e +content_filter_lang.jv=\u30b8\u30e3\u30ef\u8a9e +content_filter_lang.ka=\u30b0\u30eb\u30b8\u30a2\u8a9e +content_filter_lang.kg=\u30b3\u30f3\u30b4\u8a9e +content_filter_lang.ki=\u30ad\u30af\u30e6\u8a9e\u3001\u30ae\u30af\u30e6\u8a9e +content_filter_lang.kj=\u30af\u30a2\u30f3\u30e4\u30de\u8a9e\u3001\u30af\u30ef\u30f3\u30e4\u30de\u8a9e +content_filter_lang.kk=\u30ab\u30b6\u30d5\u30b9\u30bf\u30f3\u8a9e +content_filter_lang.kl=\u30ab\u30e9\u30fc\u30ea\u30c3\u30c8\u8a9e\u3001\u30b0\u30ea\u30fc\u30f3\u30e9\u30f3\u30c9\u8a9e +content_filter_lang.km=\u30af\u30e1\u30fc\u30eb\u8a9e +content_filter_lang.kn=\u30ab\u30f3\u30ca\u30c0\u8a9e +content_filter_lang.ko=\u671d\u9bae\u8a9e +content_filter_lang.kr=\u30ab\u30cc\u30ea\u8a9e +content_filter_lang.ks=\u30ab\u30b7\u30df\u30fc\u30eb\u8a9e +content_filter_lang.ku=\u30af\u30eb\u30c9\u8a9e +content_filter_lang.kv=\u30b3\u30df\u8a9e +content_filter_lang.kw=\u30b3\u30fc\u30f3\u30a6\u30a9\u30fc\u30eb\u8a9e +content_filter_lang.ky=\u30ad\u30eb\u30ae\u30b9\u8a9e +content_filter_lang.la=\u30e9\u30c6\u30f3\u8a9e +content_filter_lang.lb=\u30eb\u30af\u30bb\u30f3\u30d6\u30eb\u30b0\u8a9e +content_filter_lang.lg=\u30ac\u30f3\u30c0\u8a9e +content_filter_lang.li=\u30ea\u30f3\u30d6\u30eb\u30ac\u30fc\u8a9e +content_filter_lang.ln=\u30ea\u30f3\u30ac\u30e9\u8a9e +content_filter_lang.lo=\u30e9\u30aa\u8a9e +content_filter_lang.lt=\u30ea\u30c8\u30a2\u30cb\u30a2\u8a9e +content_filter_lang.lu=\u30eb\u30d0\u30fb\u30ab\u30bf\u30f3\u30ac\u8a9e +content_filter_lang.lv=\u30e9\u30c8\u30d3\u30a2\u8a9e +content_filter_lang.mg=\u30de\u30e9\u30ac\u30b7\u8a9e +content_filter_lang.mh=\u30de\u30fc\u30b7\u30e3\u30eb\u8a9e +content_filter_lang.mi=\u30de\u30aa\u30ea\u8a9e +content_filter_lang.mk=\u30de\u30b1\u30c9\u30cb\u30a2\u8a9e +content_filter_lang.ml=\u30de\u30e9\u30e4\u30fc\u30e9\u30e0\u8a9e +content_filter_lang.mn=\u30e2\u30f3\u30b4\u30eb\u8a9e +content_filter_lang.mo=\u30e2\u30eb\u30c9\u30d0\u8a9e +content_filter_lang.mr=\u30de\u30e9\u30fc\u30c6\u30a3\u30fc\u8a9e +content_filter_lang.ms=\u30de\u30e9\u30a4\u8a9e +content_filter_lang.mt=\u30de\u30eb\u30bf\u8a9e +content_filter_lang.my=\u30d3\u30eb\u30de\u8a9e +content_filter_lang.na=\u30ca\u30a6\u30eb\u8a9e +content_filter_lang.nb=\u30ce\u30eb\u30a6\u30a7\u30fc\u8a9e\uff08\u30d6\u30fc\u30af\u30e2\u30fc\u30eb\uff09 +content_filter_lang.nd=\u5317\u30f3\u30c7\u30d9\u30ec\u8a9e +content_filter_lang.ne=\u30cd\u30d1\u30fc\u30eb\u8a9e +content_filter_lang.ng=\u30c9\u30a5\u30f3\u30ac\u8a9e +content_filter_lang.nl=\u30aa\u30e9\u30f3\u30c0\u8a9e +content_filter_lang.nn=\u30ce\u30eb\u30a6\u30a7\u30fc\u8a9e\uff08\u30cb\u30e5\u30fc\u30ce\u30eb\u30b9\u30af\uff09 +content_filter_lang.no=\u30ce\u30eb\u30a6\u30a7\u30fc\u8a9e +content_filter_lang.nr=\u5357\u30f3\u30c7\u30d9\u30ec\u8a9e +content_filter_lang.nv=\u30ca\u30f4\u30a1\u30db\u8a9e +content_filter_lang.ny=\u30c1\u30a7\u30ef\u8a9e\u3001\u30cb\u30e3\u30f3\u30b8\u30e3\u8a9e +content_filter_lang.oc=\u30aa\u30c3\u30af\u8a9e +content_filter_lang.oj=\u30aa\u30b8\u30d6\u30a6\u30a7\u30fc\u8a9e +content_filter_lang.om=\u30aa\u30ed\u30e2\u8a9e +content_filter_lang.or=\u30aa\u30ea\u30e4\u30fc\u8a9e +content_filter_lang.os=\u30aa\u30bb\u30c8\u8a9e +content_filter_lang.pa=\u30d1\u30f3\u30b8\u30e3\u30d6\u8a9e +content_filter_lang.pi=\u30d1\u30fc\u30ea\u8a9e +content_filter_lang.pl=\u30dd\u30fc\u30e9\u30f3\u30c9\u8a9e +content_filter_lang.ps=\u30d1\u30b7\u30e5\u30c8\u30fc\u8a9e +content_filter_lang.pt=\u30dd\u30eb\u30c8\u30ac\u30eb\u8a9e +content_filter_lang.qu=\u30b1\u30c1\u30e5\u30a2\u8a9e +content_filter_lang.rm=\u30ec\u30c8\u30ed\u30de\u30f3\u8a9e +content_filter_lang.rn=\u30eb\u30f3\u30b2\u30a3\u8a9e +content_filter_lang.ro=\u30eb\u30fc\u30de\u30cb\u30a2\u8a9e +content_filter_lang.ru=\u30ed\u30b7\u30a2\u8a9e +content_filter_lang.rw=\u30ad\u30cb\u30e4\u30eb\u30ef\u30f3\u30c0\u8a9e +content_filter_lang.sa=\u30b5\u30f3\u30b9\u30af\u30ea\u30c3\u30c8\u8a9e +content_filter_lang.sc=\u30b5\u30eb\u30c7\u30fc\u30cb\u30e3\u8a9e +content_filter_lang.sd=\u30b7\u30f3\u30c7\u30a3\u30fc\u8a9e +content_filter_lang.se=\u5317\u30b5\u30fc\u30df\u8a9e +content_filter_lang.sg=\u30b5\u30f3\u30b4\u8a9e +content_filter_lang.si=\u30b7\u30f3\u30cf\u30e9\u8a9e +content_filter_lang.sk=\u30b9\u30ed\u30d0\u30ad\u30a2\u8a9e +content_filter_lang.sl=\u30b9\u30ed\u30d9\u30cb\u30a2\u8a9e +content_filter_lang.sm=\u30b5\u30e2\u30a2\u8a9e +content_filter_lang.sn=\u30b7\u30e7\u30ca\u8a9e +content_filter_lang.so=\u30bd\u30de\u30ea\u8a9e +content_filter_lang.sq=\u30a2\u30eb\u30d0\u30cb\u30a2\u8a9e +content_filter_lang.sr=\u30bb\u30eb\u30d3\u30a2\u8a9e +content_filter_lang.ss=\u30b9\u30ef\u30b8\u8a9e +content_filter_lang.st=\u30bd\u30c8\u8a9e\uff08\u30bb\u30bd\u30c8\u8a9e\uff09 +content_filter_lang.su=\u30b9\u30f3\u30c0\u8a9e +content_filter_lang.sv=\u30b9\u30a6\u30a7\u30fc\u30c7\u30f3\u8a9e +content_filter_lang.sw=\u30b9\u30ef\u30d2\u30ea\u8a9e +content_filter_lang.ta=\u30bf\u30df\u30eb\u8a9e +content_filter_lang.te=\u30c6\u30eb\u30b0\u8a9e +content_filter_lang.tg=\u30bf\u30b8\u30af\u8a9e +content_filter_lang.th=\u30bf\u30a4\u8a9e +content_filter_lang.ti=\u30c6\u30a3\u30b0\u30ea\u30cb\u30e3\u8a9e +content_filter_lang.tk=\u30c8\u30eb\u30af\u30e1\u30f3\u8a9e +content_filter_lang.tl=\u30bf\u30ac\u30ed\u30b0\u8a9e +content_filter_lang.tn=\u30c4\u30ef\u30ca\u8a9e +content_filter_lang.to=\u30c8\u30f3\u30ac\u8a9e\uff08\u30c8\u30f3\u30ac\u8af8\u5cf6\uff09 +content_filter_lang.tr=\u30c8\u30eb\u30b3\u8a9e +content_filter_lang.tr=\u30c8\u30eb\u30b3\u8a9e +content_filter_lang.ts=\u30c4\u30a9\u30f3\u30ac\u8a9e +content_filter_lang.tt=\u30bf\u30bf\u30fc\u30eb\u8a9e +content_filter_lang.tw=\u30c8\u30a6\u30a3\u8a9e +content_filter_lang.ty=\u30bf\u30d2\u30c1\u8a9e +content_filter_lang.ug=\u30a6\u30a4\u30b0\u30eb\u8a9e +content_filter_lang.uk=\u30a6\u30af\u30e9\u30a4\u30ca\u8a9e +content_filter_lang.ur=\u30a6\u30eb\u30c9\u30a5\u30fc\u8a9e +content_filter_lang.uz=\u30a6\u30ba\u30d9\u30af\u8a9e +content_filter_lang.ve=\u30d9\u30f3\u30c0\u8a9e +content_filter_lang.vi=\u30d9\u30c8\u30ca\u30e0\u8a9e +content_filter_lang.vo=\u30dc\u30e9\u30d4\u30e5\u30af\u8a9e +content_filter_lang.wa=\u30ef\u30ed\u30f3\u8a9e +content_filter_lang.wo=\u30a6\u30a9\u30ed\u30d5\u8a9e +content_filter_lang.xh=\u30b3\u30b5\u8a9e +content_filter_lang.yi=\u30a4\u30c7\u30a3\u30c3\u30b7\u30e5\u8a9e +content_filter_lang.yo=\u30e8\u30eb\u30d0\u8a9e +content_filter_lang.za=\u30c1\u30ef\u30f3\u8a9e +content_filter_lang.zh=\u4e2d\u56fd\u8a9e +content_filter_lang.zu=\u30ba\u30fc\u30eb\u30fc\u8a9e + +## Duplicate messages to take in account the old +## ISO code. +content_filter_lang.iw=\u30d8\u30d6\u30e9\u30a4\u8a9e +content_filter_lang.in=\u30a4\u30f3\u30c9\u30cd\u30b7\u30a2\u8a9e +content_filter_lang.ji=\u30a4\u30c7\u30a3\u30c3\u30b7\u30e5\u8a9e diff --git a/config/alfresco/messages/content-model.properties b/config/alfresco/messages/content-model.properties index 52e935e1d1..0bdec8371d 100644 --- a/config/alfresco/messages/content-model.properties +++ b/config/alfresco/messages/content-model.properties @@ -34,20 +34,72 @@ cm_contentmodel.type.cm_person.title=Person cm_contentmodel.type.cm_person.description=Person cm_contentmodel.property.cm_userName.title=User Name -cm_contentmodel.property.cm_userName.description=The Person's user name +cm_contentmodel.property.cm_userName.description=The person's user name cm_contentmodel.property.cm_homeFolder.title=Home Folder -cm_contentmodel.property.cm_homeFolder.description=The Person's home folder +cm_contentmodel.property.cm_homeFolder.description=The person's home folder cm_contentmodel.property.cm_firstName.title=First Name -cm_contentmodel.property.cm_firstName.description=The Person's first name +cm_contentmodel.property.cm_firstName.description=The person's first name cm_contentmodel.property.cm_lastName.title=Last Name -cm_contentmodel.property.cm_lastName.description=The Person's last name +cm_contentmodel.property.cm_lastName.description=The person's last name cm_contentmodel.property.cm_middleName.title=Middle Name -cm_contentmodel.property.cm_middleName.description=The Person's middle name +cm_contentmodel.property.cm_middleName.description=The person's middle name cm_contentmodel.property.cm_email.title=E-mail Address -cm_contentmodel.property.cm_email.description=The Person's e-mail address -cm_contentmodel.property.cm_organizationId.title=Organization -cm_contentmodel.property.cm_organizationId.description=The Person's organization - +cm_contentmodel.property.cm_email.description=The person's e-mail address +cm_contentmodel.property.cm_homeFolderProvider.title=Home Folder Provider +cm_contentmodel.property.cm_homeFolderProvider.description=Home Folder Provider +cm_contentmodel.property.cm_defaultHomeFolderPath.title=Home Folder Path +cm_contentmodel.property.cm_defaultHomeFolderPath.description=Path to the Person's home folder +cm_contentmodel.property.cm_presenceProvider.title=Presense Provider +cm_contentmodel.property.cm_presenceProvider.description=Presense Provider +cm_contentmodel.property.cm_presenceUsername.title=Presense User Name +cm_contentmodel.property.cm_presenceUsername.description=Presense User Name +cm_contentmodel.property.cm_jobtitle.title=Job Title +cm_contentmodel.property.cm_jobtitle.description=The person's job title +cm_contentmodel.property.cm_location.title=Location +cm_contentmodel.property.cm_location.description=The person's location +cm_contentmodel.property.cm_persondescription.title=Summary +cm_contentmodel.property.cm_persondescription.description=The person's summary +cm_contentmodel.property.cm_telephone.title=Telephone +cm_contentmodel.property.cm_telephone.description=The person's telephone number +cm_contentmodel.property.cm_mobile.title=Mobile +cm_contentmodel.property.cm_mobile.description=The person's mobile telephone number +cm_contentmodel.property.cm_organizationId.title=Organization Id +cm_contentmodel.property.cm_organizationId.description=The person's organization id +cm_contentmodel.property.cm_organization.title=Company +cm_contentmodel.property.cm_organization.description=The person's company +cm_contentmodel.property.cm_companyaddress1.title=Address +cm_contentmodel.property.cm_companyaddress1.description=First line of person's company address +cm_contentmodel.property.cm_companyaddress2.title=Address Line 2 +cm_contentmodel.property.cm_companyaddress2.description=Second line of person's company address +cm_contentmodel.property.cm_companyaddress3.title=Address Line 3 +cm_contentmodel.property.cm_companyaddress3.description=Third line of person's company address +cm_contentmodel.property.cm_companypostcode.title=Post Code +cm_contentmodel.property.cm_companypostcode.description=The person's company post code +cm_contentmodel.property.cm_companytelephone.title=Telephone +cm_contentmodel.property.cm_companytelephone.description=The person's company telephone number +cm_contentmodel.property.cm_companyfax.title=Fax +cm_contentmodel.property.cm_companyfax.description=The person's company fax number +cm_contentmodel.property.cm_companyemail.title=Email +cm_contentmodel.property.cm_companyemail.description=The person's company email address +cm_contentmodel.property.cm_skype.title=Skype +cm_contentmodel.property.cm_skype.description=The person's Skype user id +cm_contentmodel.property.cm_instantmsg.title=IM +cm_contentmodel.property.cm_instantmsg.description=The person's instant messaging user id +cm_contentmodel.property.cm_userStatus.title=Status +cm_contentmodel.property.cm_userStatus.description=The person's current status +cm_contentmodel.property.cm_userStatusTime.title=Status Time +cm_contentmodel.property.cm_userStatusTime.description=The time the person's status was last updated +cm_contentmodel.property.cm_googleusername.title=Google User Name +cm_contentmodel.property.cm_googleusername.description=The person's Google user name +cm_contentmodel.property.cm_sizeCurrent.title=Usage +cm_contentmodel.property.cm_sizeCurrent.description=The amount of disk space the person is using +cm_contentmodel.property.cm_sizeQuota.title=Quota +cm_contentmodel.property.cm_sizeQuota.description=The maximum amount of disk space the person can use +cm_contentmodel.property.cm_emailFeedId.title=Email Feed Id +cm_contentmodel.property.cm_emailFeedId.description=The id of the person's email feed id +cm_contentmodel.association.cm_avatar.title=Avatar +cm_contentmodel.association.cm_avatar.description=The person's avatar image + cm_contentmodel.type.cm_category_root.title=Category Root cm_contentmodel.type.cm_category_root.description=Root Category cm_contentmodel.association.cm_categories.title=Categories diff --git a/config/alfresco/messages/content-model_de.properties b/config/alfresco/messages/content-model_de.properties index fff4f0cf1c..ecc7d85b03 100755 --- a/config/alfresco/messages/content-model_de.properties +++ b/config/alfresco/messages/content-model_de.properties @@ -36,7 +36,7 @@ cm_contentmodel.type.cm_person.description=Person cm_contentmodel.property.cm_userName.title=Benutzername cm_contentmodel.property.cm_userName.description=Benutzername der Person cm_contentmodel.property.cm_homeFolder.title=Home Ordner -cm_contentmodel.property.cm_homeFolder.description=Home Ordner der Person +cm_contentmodel.property.cm_homeFolder.description=Home-Ordner der Person cm_contentmodel.property.cm_firstName.title=Vorname cm_contentmodel.property.cm_firstName.description=Vorname der Person cm_contentmodel.property.cm_lastName.title=Nachname @@ -44,12 +44,64 @@ cm_contentmodel.property.cm_lastName.description=Nachname der Person cm_contentmodel.property.cm_middleName.title=Zweitname cm_contentmodel.property.cm_middleName.description=Zweitname der Person cm_contentmodel.property.cm_email.title=E-Mail Adresse -cm_contentmodel.property.cm_email.description=E-Mail Adresse der Person -cm_contentmodel.property.cm_organizationId.title=Organisation -cm_contentmodel.property.cm_organizationId.description=Organisation der Person - +cm_contentmodel.property.cm_email.description=E-Mail-Adresse der Person +cm_contentmodel.property.cm_homeFolderProvider.title=Home-Ordnerprovider +cm_contentmodel.property.cm_homeFolderProvider.description=Home-Ordnerprovider +cm_contentmodel.property.cm_defaultHomeFolderPath.title=Home-Ordnerpfad +cm_contentmodel.property.cm_defaultHomeFolderPath.description=Pfad zum Home-Ordner der Person +cm_contentmodel.property.cm_presenceProvider.title=Pr\u00e4senzprovider +cm_contentmodel.property.cm_presenceProvider.description=Pr\u00e4senzprovider +cm_contentmodel.property.cm_presenceUsername.title=Pr\u00e4senzbenutzername +cm_contentmodel.property.cm_presenceUsername.description=Pr\u00e4senzbenutzername +cm_contentmodel.property.cm_jobtitle.title=Jobtitel +cm_contentmodel.property.cm_jobtitle.description=Jobtitel der Person +cm_contentmodel.property.cm_location.title=Speicherort +cm_contentmodel.property.cm_location.description=Speicherort der Person +cm_contentmodel.property.cm_persondescription.title=Zusammenfassung +cm_contentmodel.property.cm_persondescription.description=Zusammenfassung der Person +cm_contentmodel.property.cm_telephone.title=Telefon +cm_contentmodel.property.cm_telephone.description=Telefonnummer der Person +cm_contentmodel.property.cm_mobile.title=Mobil +cm_contentmodel.property.cm_mobile.description=Mobiltelefonnummer der Person +cm_contentmodel.property.cm_organizationId.title=Organisations-ID +cm_contentmodel.property.cm_organizationId.description=Organisations-ID der Person +cm_contentmodel.property.cm_organization.title=Firma +cm_contentmodel.property.cm_organization.description=Firma der Person +cm_contentmodel.property.cm_companyaddress1.title=Adresse +cm_contentmodel.property.cm_companyaddress1.description=Erste Zeile der Firmenadresse der Person +cm_contentmodel.property.cm_companyaddress2.title=Adresse Zeile 2 +cm_contentmodel.property.cm_companyaddress2.description=Zweite Zeile der Firmenadresse der Person +cm_contentmodel.property.cm_companyaddress3.title=Adresse Zeile 3 +cm_contentmodel.property.cm_companyaddress3.description=Dritte Zeile der Firmenadresse der Person +cm_contentmodel.property.cm_companypostcode.title=Postleitzahl +cm_contentmodel.property.cm_companypostcode.description=Postleitzahl der Firma der Person +cm_contentmodel.property.cm_companytelephone.title=Telefon +cm_contentmodel.property.cm_companytelephone.description=Telefonnummer der Firma der Person +cm_contentmodel.property.cm_companyfax.title=Fax +cm_contentmodel.property.cm_companyfax.description=Faxnummer der Firma der Person +cm_contentmodel.property.cm_companyemail.title=E-Mail +cm_contentmodel.property.cm_companyemail.description=Firmen-E-Mail-Adresse der Person +cm_contentmodel.property.cm_skype.title=Skype +cm_contentmodel.property.cm_skype.description=Skype-Benutzer-ID der Person +cm_contentmodel.property.cm_instantmsg.title=IM +cm_contentmodel.property.cm_instantmsg.description=Der Benutzername f\u00fcr Instant Messaging der Person +cm_contentmodel.property.cm_userStatus.title=Status +cm_contentmodel.property.cm_userStatus.description=Der aktuelle Status der Person +cm_contentmodel.property.cm_userStatusTime.title=Statuszeit +cm_contentmodel.property.cm_userStatusTime.description=Wann der Status der Person zuletzt aktualisiert wurde +cm_contentmodel.property.cm_googleusername.title=Google-Benutzername +cm_contentmodel.property.cm_googleusername.description=Der Google-Benutzername der Person +cm_contentmodel.property.cm_sizeCurrent.title=Gebrauch +cm_contentmodel.property.cm_sizeCurrent.description=Speicherplatz, den die Person verbraucht +cm_contentmodel.property.cm_sizeQuota.title=Kontingent +cm_contentmodel.property.cm_sizeQuota.description=Maximaler Speicherplatz, den die Person verbrauchen darf +cm_contentmodel.property.cm_emailFeedId.title=E-Mail-Feed-ID +cm_contentmodel.property.cm_emailFeedId.description=Die ID f\u00fcr den E-Mail-Feed der Person +cm_contentmodel.association.cm_avatar.title=Avatar +cm_contentmodel.association.cm_avatar.description=Das Avatar-Bild der Person + cm_contentmodel.type.cm_category_root.title=Kategorie Root -cm_contentmodel.type.cm_category_root.description=Root-Kategorie +cm_contentmodel.type.cm_category_root.description=Root-Kategorie cm_contentmodel.association.cm_categories.title=Kategorien cm_contentmodel.association.cm_categories.description=Kategorien innerhalb der Kategorie Root @@ -152,8 +204,8 @@ cm_contentmodel.aspect.cm_replacable.description=Ersetzbar cm_contentmodel.association.cm_replaces.title=Ersetzt cm_contentmodel.association.cm_replaces.description=Ersetzt -cm_contentmodel.aspect.cm_effectivity.title=Wirksamkeit -cm_contentmodel.aspect.cm_effectivity.description=Wirksamkeit +cm_contentmodel.aspect.cm_effectivity.title=Effektivit\u00e4t +cm_contentmodel.aspect.cm_effectivity.description=Effektivit\u00e4t cm_contentmodel.property.cm_from.title=Wirksam ab cm_contentmodel.property.cm_from.description=Wirksam ab cm_contentmodel.property.cm_to.title=Wirksam bis @@ -179,8 +231,8 @@ cm_contentmodel.aspect.cm_workingcopy.description=Arbeitskopie cm_contentmodel.property.cm_workingCopyOwner.title=Eigent\u00fcmer der Arbeitskopie cm_contentmodel.property.cm_workingCopyOwner.description=Eigent\u00fcmer der Arbeitskopie -cm_contentmodel.aspect.cm_versionable.title=Versionierbar -cm_contentmodel.aspect.cm_versionable.description=Versionierbar +cm_contentmodel.aspect.cm_versionable.title=Versionsf\u00e4hig +cm_contentmodel.aspect.cm_versionable.description=Versionsf\u00e4hig cm_contentmodel.property.cm_versionLabel.title=Versionslabel cm_contentmodel.property.cm_versionLabel.description=Versionslabel cm_contentmodel.property.cm_autoVersion.title=Auto Version @@ -275,4 +327,3 @@ cm_contentmodel.property.exif_yResolution.description=Vertikale Aufl\u00f6sung i cm_contentmodel.property.exif_resolutionUnit.title=Aufl\u00f6sungseinheit cm_contentmodel.property.exif_resolutionUnit.description=Einheit f\u00fcr die horizontale und vertikale Aufl\u00f6sung - diff --git a/config/alfresco/messages/content-model_es.properties b/config/alfresco/messages/content-model_es.properties index 323fe075a5..f96c805061 100755 --- a/config/alfresco/messages/content-model_es.properties +++ b/config/alfresco/messages/content-model_es.properties @@ -1,6 +1,6 @@ # Display labels for Content Domain Model -cm_contentmodel.description=Modelo de dominio de contenido de Alfresco +cm_contentmodel.description=Modelo de dominio de contenido de Alfresco cm_contentmodel.type.cm_object.title=Objeto cm_contentmodel.type.cm_object.description=Objeto b\u00e1sico de dominio de contenido @@ -39,15 +39,67 @@ cm_contentmodel.property.cm_homeFolder.title=Carpeta de inicio cm_contentmodel.property.cm_homeFolder.description=Carpeta de inicio de la persona cm_contentmodel.property.cm_firstName.title=Nombre cm_contentmodel.property.cm_firstName.description=Nombre de la persona -cm_contentmodel.property.cm_lastName.title=Apellido -cm_contentmodel.property.cm_lastName.description=Apellido de la persona +cm_contentmodel.property.cm_lastName.title=Apellidos +cm_contentmodel.property.cm_lastName.description=Apellidos de la persona cm_contentmodel.property.cm_middleName.title=Segundo nombre cm_contentmodel.property.cm_middleName.description=Segundo nombre de la persona cm_contentmodel.property.cm_email.title=Direcci\u00f3n de correo electr\u00f3nico -cm_contentmodel.property.cm_email.description=Direcci\u00f3n email de la persona -cm_contentmodel.property.cm_organizationId.title=Organizaci\u00f3n -cm_contentmodel.property.cm_organizationId.description=Organizaci\u00f3n de la persona - +cm_contentmodel.property.cm_email.description=Direcci\u00f3n de correo electr\u00f3nico de la persona +cm_contentmodel.property.cm_homeFolderProvider.title=Carpeta de inicio de proveedor +cm_contentmodel.property.cm_homeFolderProvider.description=Carpeta de inicio de proveedor +cm_contentmodel.property.cm_defaultHomeFolderPath.title=Ruta de la carpeta de inicio +cm_contentmodel.property.cm_defaultHomeFolderPath.description=Ruta a la carpeta de inicio de la persona +cm_contentmodel.property.cm_presenceProvider.title=Proveedor de Presense +cm_contentmodel.property.cm_presenceProvider.description=Proveedor de Presense +cm_contentmodel.property.cm_presenceUsername.title=Nombre de usuario de Presense +cm_contentmodel.property.cm_presenceUsername.description=Nombre de usuario de Presense +cm_contentmodel.property.cm_jobtitle.title=Cargo +cm_contentmodel.property.cm_jobtitle.description=Cargo de la persona +cm_contentmodel.property.cm_location.title=Ubicaci\u00f3n +cm_contentmodel.property.cm_location.description=Ubicaci\u00f3n de la persona +cm_contentmodel.property.cm_persondescription.title=Resumen +cm_contentmodel.property.cm_persondescription.description=Resumen de la persona +cm_contentmodel.property.cm_telephone.title=Tel\u00e9fono +cm_contentmodel.property.cm_telephone.description=N\u00famero de tel\u00e9fono de la persona +cm_contentmodel.property.cm_mobile.title=M\u00f3vil +cm_contentmodel.property.cm_mobile.description=N\u00famero de tel\u00e9fono m\u00f3vil de la persona +cm_contentmodel.property.cm_organizationId.title=Id. organizaci\u00f3n +cm_contentmodel.property.cm_organizationId.description=Identificador de la organizaci\u00f3n de la persona +cm_contentmodel.property.cm_organization.title=Empresa +cm_contentmodel.property.cm_organization.description=Empresa de la persona +cm_contentmodel.property.cm_companyaddress1.title=Direcci\u00f3n +cm_contentmodel.property.cm_companyaddress1.description=Primera l\u00ednea de la direcci\u00f3n de la empresa de la persona +cm_contentmodel.property.cm_companyaddress2.title=Direcci\u00f3n l\u00ednea 2 +cm_contentmodel.property.cm_companyaddress2.description=Segunda l\u00ednea de la direcci\u00f3n de la empresa de la persona +cm_contentmodel.property.cm_companyaddress3.title=Direcci\u00f3n l\u00ednea 3 +cm_contentmodel.property.cm_companyaddress3.description=Tercera l\u00ednea de la direcci\u00f3n de la empresa de la persona +cm_contentmodel.property.cm_companypostcode.title=C\u00f3digo postal +cm_contentmodel.property.cm_companypostcode.description=C\u00f3digo postal de la empresa de la persona +cm_contentmodel.property.cm_companytelephone.title=Tel\u00e9fono +cm_contentmodel.property.cm_companytelephone.description=N\u00famero de tel\u00e9fono de la empresa de la persona +cm_contentmodel.property.cm_companyfax.title=Fax +cm_contentmodel.property.cm_companyfax.description=N\u00famero de fax de la empresa de la persona +cm_contentmodel.property.cm_companyemail.title=Email +cm_contentmodel.property.cm_companyemail.description=Direcci\u00f3n de correo electr\u00f3nico de la empresa de la persona +cm_contentmodel.property.cm_skype.title=Skype +cm_contentmodel.property.cm_skype.description=Identificador de usuario de Skype de la persona +cm_contentmodel.property.cm_instantmsg.title=Mensajer\u00eda instant\u00e1nea +cm_contentmodel.property.cm_instantmsg.description=Identificador de usuario de mensajer\u00eda instant\u00e1nea de la persona +cm_contentmodel.property.cm_userStatus.title=Estado +cm_contentmodel.property.cm_userStatus.description=Estado actual de la persona +cm_contentmodel.property.cm_userStatusTime.title=Hora del estado +cm_contentmodel.property.cm_userStatusTime.description=La hora de la actualizaci\u00f3n m\u00e1s reciente del estado de la persona +cm_contentmodel.property.cm_googleusername.title=Nombre de usuario de Google +cm_contentmodel.property.cm_googleusername.description=Nombre de usuario de Google de la persona +cm_contentmodel.property.cm_sizeCurrent.title=Uso +cm_contentmodel.property.cm_sizeCurrent.description=La cantidad de espacio del disco que usa la persona +cm_contentmodel.property.cm_sizeQuota.title=Cuota +cm_contentmodel.property.cm_sizeQuota.description=La cantidad m\u00e1xima de espacio del disco que puede usar la persona +cm_contentmodel.property.cm_emailFeedId.title=Id. canal de email +cm_contentmodel.property.cm_emailFeedId.description=El identificador del canal de correo electr\u00f3nico de la persona +cm_contentmodel.association.cm_avatar.title=Avatar +cm_contentmodel.association.cm_avatar.description=La imagen de avatar de la persona + cm_contentmodel.type.cm_category_root.title=Ra\u00edz de categor\u00eda cm_contentmodel.type.cm_category_root.description=Categor\u00eda ra\u00edz cm_contentmodel.association.cm_categories.title=Categor\u00edas @@ -63,7 +115,7 @@ cm_contentmodel.aspect.cm_titled.description=Titulada cm_contentmodel.property.cm_title.title=T\u00edtulo cm_contentmodel.property.cm_title.description=T\u00edtulo de contenido cm_contentmodel.property.cm_description.title=Descripci\u00f3n -cm_contentmodel.property.cm_description.description=Descripci\u00f3n de contenido +cm_contentmodel.property.cm_description.description=Descripci\u00f3n de contenido cm_contentmodel.aspect.cm_auditable.title=Auditable cm_contentmodel.aspect.cm_auditable.description=Auditable @@ -152,8 +204,8 @@ cm_contentmodel.aspect.cm_replacable.description=Reemplazable cm_contentmodel.association.cm_replaces.title=Sustituye cm_contentmodel.association.cm_replaces.description=Sustituye -cm_contentmodel.aspect.cm_effectivity.title=Vigencia -cm_contentmodel.aspect.cm_effectivity.description=Vigencia +cm_contentmodel.aspect.cm_effectivity.title=Efectividad +cm_contentmodel.aspect.cm_effectivity.description=Efectividad cm_contentmodel.property.cm_from.title=Vigente desde cm_contentmodel.property.cm_from.description=Vigente desde cm_contentmodel.property.cm_to.title=Vigente hasta @@ -249,7 +301,7 @@ cm_contentmodel.property.exif_dateTimeOriginal.description=Fecha y hora de gener cm_contentmodel.property.exif_pixelXDimension.title=Ancho de imagen cm_contentmodel.property.exif_pixelXDimension.description=El ancho de la imagen en p\u00edxeles cm_contentmodel.property.exif_pixelYDimension.title=Altura de imagen -cm_contentmodel.property.exif_pixelYDimension.description=lo alto de la imagen en p\u00edxeles +cm_contentmodel.property.exif_pixelYDimension.description=El alto de la imagen en p\u00edxeles cm_contentmodel.property.exif_exposureTime.title=Tiempo de exposici\u00f3n cm_contentmodel.property.exif_exposureTime.description=Tiempo de exposici\u00f3n, en segundos cm_contentmodel.property.exif_fNumber.title=N\u00famero F @@ -274,3 +326,4 @@ cm_contentmodel.property.exif_yResolution.title=Resoluci\u00f3n vertical cm_contentmodel.property.exif_yResolution.description=Resoluci\u00f3n vertical en p\u00edxeles por unidad cm_contentmodel.property.exif_resolutionUnit.title=Unidad de resoluci\u00f3n cm_contentmodel.property.exif_resolutionUnit.description=Unidad utilizada para la resoluci\u00f3n horizontal y vertical + diff --git a/config/alfresco/messages/content-model_fr.properties b/config/alfresco/messages/content-model_fr.properties index 5682344f1e..28f4e3044f 100755 --- a/config/alfresco/messages/content-model_fr.properties +++ b/config/alfresco/messages/content-model_fr.properties @@ -1,9 +1,9 @@ # Display labels for Content Domain Model -cm_contentmodel.description=Mod\u00e8le de contenu Alfresco +cm_contentmodel.description=Mod\u00e8le de domaine de contenu Alfresco cm_contentmodel.type.cm_object.title=Objet -cm_contentmodel.type.cm_object.description=Objet domaine du contenu de base +cm_contentmodel.type.cm_object.description=Objet de domaine de contenu de base cm_contentmodel.property.cm_name.title=Nom cm_contentmodel.property.cm_name.description=Nom @@ -19,16 +19,16 @@ cm_contentmodel.type.cm_content.description=Objet de contenu de base cm_contentmodel.property.cm_content.title=Contenu cm_contentmodel.property.cm_content.description=Contenu -cm_contentmodel.type.cm_linkfile.title=Lien vers un Fichier -cm_contentmodel.type.cm_linkfile.description=Lien vers un autre Fichier -cm_contentmodel.property.cm_path.title=Chemin du Lien vers le Fichier -cm_contentmodel.property.cm_path.description=Chemin du Fichier li\u00e9 +cm_contentmodel.type.cm_linkfile.title=Lien vers un fichier +cm_contentmodel.type.cm_linkfile.description=Lien vers un autre fichier +cm_contentmodel.property.cm_path.title=Chemin du lien vers le fichier +cm_contentmodel.property.cm_path.description=Chemin du fichier li\u00e9 -cm_contentmodel.type.cm_savedquery.title=Requ\u00eate sauv\u00e9e -cm_contentmodel.type.cm_savedquery.description=Requ\u00eate sauv\u00e9e +cm_contentmodel.type.cm_savedquery.title=Requ\u00eate enregistr\u00e9e +cm_contentmodel.type.cm_savedquery.description=Requ\u00eate enregistr\u00e9e cm_contentmodel.type.cm_systemfolder.title=Dossier syst\u00e8me -cm_contentmodel.type.cm_systemfolder.description=Dossier contenant les \u00e9l\u00e9ments syst\u00e8mes +cm_contentmodel.type.cm_systemfolder.description=Dossier contenant les \u00e9l\u00e9ments syst\u00e8me cm_contentmodel.type.cm_person.title=Personne cm_contentmodel.type.cm_person.description=Personne @@ -43,40 +43,92 @@ cm_contentmodel.property.cm_lastName.title=Nom cm_contentmodel.property.cm_lastName.description=Nom de l'utilisateur cm_contentmodel.property.cm_middleName.title=Autres pr\u00e9noms cm_contentmodel.property.cm_middleName.description=Autres pr\u00e9noms de l'utilisateur -cm_contentmodel.property.cm_email.title=Adresse Mel -cm_contentmodel.property.cm_email.description=Adresse mel de l'utilisateur -cm_contentmodel.property.cm_organizationId.title=Soci\u00e9t\u00e9 -cm_contentmodel.property.cm_organizationId.description=Soci\u00e9t\u00e9 de l'utilisateur - -cm_contentmodel.type.cm_category_root.title=Cat\u00e9gorie Racine -cm_contentmodel.type.cm_category_root.description=Cat\u00e9gorie Racine +cm_contentmodel.property.cm_email.title=Adresse e-mail +cm_contentmodel.property.cm_email.description=Adresse e-mail de l'utilisateur +cm_contentmodel.property.cm_homeFolderProvider.title=Fournisseur du dossier personnel +cm_contentmodel.property.cm_homeFolderProvider.description=Fournisseur du dossier personnel +cm_contentmodel.property.cm_defaultHomeFolderPath.title=Chemin du dossier personnel +cm_contentmodel.property.cm_defaultHomeFolderPath.description=Chemin d'acc\u00e8s au dossier personnel de l'utilisateur +cm_contentmodel.property.cm_presenceProvider.title=Fournisseur d'indication de pr\u00e9sence +cm_contentmodel.property.cm_presenceProvider.description=Fournisseur de l'indication de pr\u00e9sence +cm_contentmodel.property.cm_presenceUsername.title=Nom d'utilisateur pour l'indicateur de pr\u00e9sence +cm_contentmodel.property.cm_presenceUsername.description=Nom de l'utilisateur pour l'indicateur de pr\u00e9sence +cm_contentmodel.property.cm_jobtitle.title=Intitul\u00e9 du poste +cm_contentmodel.property.cm_jobtitle.description=Intitul\u00e9 du poste de l'utilisateur +cm_contentmodel.property.cm_location.title=Emplacement +cm_contentmodel.property.cm_location.description=Emplacement de l'utilisateur +cm_contentmodel.property.cm_persondescription.title=R\u00e9sum\u00e9 +cm_contentmodel.property.cm_persondescription.description=R\u00e9sum\u00e9 concernant l'utilisateur +cm_contentmodel.property.cm_telephone.title=T\u00e9l\u00e9phone +cm_contentmodel.property.cm_telephone.description=Num\u00e9ro de t\u00e9l\u00e9phone de l'utilisateur +cm_contentmodel.property.cm_mobile.title=T\u00e9l\u00e9phone mobile +cm_contentmodel.property.cm_mobile.description=Num\u00e9ro de t\u00e9l\u00e9phone mobile de l'utilisateur +cm_contentmodel.property.cm_organizationId.title=ID soci\u00e9t\u00e9 +cm_contentmodel.property.cm_organizationId.description=ID de la soci\u00e9t\u00e9 de l'utilisateur +cm_contentmodel.property.cm_organization.title=Soci\u00e9t\u00e9 +cm_contentmodel.property.cm_organization.description=Soci\u00e9t\u00e9 de l'utilisateur +cm_contentmodel.property.cm_companyaddress1.title=Adresse +cm_contentmodel.property.cm_companyaddress1.description=Premi\u00e8re ligne d'adresse de la soci\u00e9t\u00e9 de l'utilisateur +cm_contentmodel.property.cm_companyaddress2.title=Adresse (ligne 2) +cm_contentmodel.property.cm_companyaddress2.description=Deuxi\u00e8me ligne d'adresse de la soci\u00e9t\u00e9 de l'utilisateur +cm_contentmodel.property.cm_companyaddress3.title=Adresse (ligne 3) +cm_contentmodel.property.cm_companyaddress3.description=Troisi\u00e8me ligne d'adresse de la soci\u00e9t\u00e9 de l'utilisateur +cm_contentmodel.property.cm_companypostcode.title=Code postal +cm_contentmodel.property.cm_companypostcode.description=Code postal de la soci\u00e9t\u00e9 de l'utilisateur +cm_contentmodel.property.cm_companytelephone.title=T\u00e9l\u00e9phone +cm_contentmodel.property.cm_companytelephone.description=Num\u00e9ro de t\u00e9l\u00e9phone de la soci\u00e9t\u00e9 de l'utilisateur +cm_contentmodel.property.cm_companyfax.title=Fax +cm_contentmodel.property.cm_companyfax.description=Num\u00e9ro de fax de la soci\u00e9t\u00e9 de l'utilisateur +cm_contentmodel.property.cm_companyemail.title=E-mail +cm_contentmodel.property.cm_companyemail.description=Adresse e-mail de la soci\u00e9t\u00e9 de l'utilisateur +cm_contentmodel.property.cm_skype.title=Skype +cm_contentmodel.property.cm_skype.description=Identifiant Skype de l'utilisateur +cm_contentmodel.property.cm_instantmsg.title=Messagerie instantan\u00e9e +cm_contentmodel.property.cm_instantmsg.description=ID de messagerie instantan\u00e9e de l'utilisateur +cm_contentmodel.property.cm_userStatus.title=Statut +cm_contentmodel.property.cm_userStatus.description=Statut actuel de l'utilisateur +cm_contentmodel.property.cm_userStatusTime.title=Heure du statut +cm_contentmodel.property.cm_userStatusTime.description=Heure de la derni\u00e8re mise \u00e0 jour du statut de l'utilisateur +cm_contentmodel.property.cm_googleusername.title=Nom d'utilisateur Google +cm_contentmodel.property.cm_googleusername.description=Nom de l'utilisateur pour Google +cm_contentmodel.property.cm_sizeCurrent.title=Utilisation +cm_contentmodel.property.cm_sizeCurrent.description=Volume d'espace disque utilis\u00e9 par l'utilisateur +cm_contentmodel.property.cm_sizeQuota.title=Quota +cm_contentmodel.property.cm_sizeQuota.description=Volume d'espace disque maximum pouvant \u00eatre utilis\u00e9 par l'utilisateur +cm_contentmodel.property.cm_emailFeedId.title=ID de flux d'e-mail +cm_contentmodel.property.cm_emailFeedId.description=Identifiant du flux d'e-mail de l'utilisateur +cm_contentmodel.association.cm_avatar.title=Avatar +cm_contentmodel.association.cm_avatar.description=Avatar de l'utilisateur + +cm_contentmodel.type.cm_category_root.title=Cat\u00e9gorie racine +cm_contentmodel.type.cm_category_root.description=Cat\u00e9gorie racine cm_contentmodel.association.cm_categories.title=Cat\u00e9gories -cm_contentmodel.association.cm_categories.description=Cat\u00e9gories de la Cat\u00e9gorie Racine +cm_contentmodel.association.cm_categories.description=Cat\u00e9gories de la cat\u00e9gorie racine cm_contentmodel.type.cm_category.title=Cat\u00e9gorie cm_contentmodel.type.cm_category.description=Cat\u00e9gorie cm_contentmodel.association.cm_subcategories.title=Cat\u00e9gories -cm_contentmodel.association.cm_subcategories.description=Sous-cat\u00e9gories de la Cat\u00e9gorie +cm_contentmodel.association.cm_subcategories.description=Sous-cat\u00e9gories de la cat\u00e9gorie cm_contentmodel.aspect.cm_titled.title=Titre cm_contentmodel.aspect.cm_titled.description=Titre cm_contentmodel.property.cm_title.title=Titre -cm_contentmodel.property.cm_title.description=Titre du Contenu +cm_contentmodel.property.cm_title.description=Titre du contenu cm_contentmodel.property.cm_description.title=Description -cm_contentmodel.property.cm_description.description=Description du Contenu +cm_contentmodel.property.cm_description.description=Description du contenu cm_contentmodel.aspect.cm_auditable.title=V\u00e9rifiable cm_contentmodel.aspect.cm_auditable.description=V\u00e9rifiable cm_contentmodel.property.cm_created.title=Date de cr\u00e9ation cm_contentmodel.property.cm_created.description=Date de cr\u00e9ation cm_contentmodel.property.cm_creator.title=Cr\u00e9ateur -cm_contentmodel.property.cm_creator.description=Celui qui a cr\u00e9e cet \u00e9l\u00e9ment -cm_contentmodel.property.cm_modified.title=Date de Modification +cm_contentmodel.property.cm_creator.description=Personne qui a cr\u00e9\u00e9 cet \u00e9l\u00e9ment +cm_contentmodel.property.cm_modified.title=Date de modification cm_contentmodel.property.cm_modified.description=Quand cet \u00e9l\u00e9ment a \u00e9t\u00e9 modifi\u00e9 pour la derni\u00e8re fois cm_contentmodel.property.cm_modifier.title=Modificateur cm_contentmodel.property.cm_modifier.description=Qui a modifi\u00e9 cet \u00e9l\u00e9ment en dernier -cm_contentmodel.property.cm_accessed.title=Date de dernier acc\u00e8sLast Accessed Date -cm_contentmodel.property.cm_accessed.description=Quand cet \u00e9l\u00e9ment a \u00e9t\u00e9 acc\u00e9d\u00e9 pour la derni\u00e8re fois +cm_contentmodel.property.cm_accessed.title=Date de dernier acc\u00e8s +cm_contentmodel.property.cm_accessed.description=Quand cet \u00e9l\u00e9ment a fait l'objet d'un acc\u00e8s pour la derni\u00e8re fois cm_contentmodel.aspect.cm_author.title=Auteur cm_contentmodel.aspect.cm_author.description=Auteur @@ -85,8 +137,8 @@ cm_contentmodel.property.cm_author.description=Auteur cm_contentmodel.aspect.cm_localizable.title=Localisable cm_contentmodel.aspect.cm_localizable.description=Localisable -cm_contentmodel.property.cm_locale.title=Local -cm_contentmodel.property.cm_locale.description=Local +cm_contentmodel.property.cm_locale.title=Environnement local +cm_contentmodel.property.cm_locale.description=Environnement local cm_contentmodel.aspect.cm_translatable.title=Traduisible cm_contentmodel.aspect.cm_translatable.description=Traduisible @@ -98,15 +150,15 @@ cm_contentmodel.aspect.cm_transformable.description=Transformable cm_contentmodel.association.cm_formats.title=Formats cm_contentmodel.association.cm_formats.description=El\u00e9ments transform\u00e9s -cm_contentmodel.aspect.cm_templatable.title=Transformable en Mod\u00e8le -cm_contentmodel.aspect.cm_templatable.description=Transformable en Mod\u00e8le +cm_contentmodel.aspect.cm_templatable.title=Peut \u00eatre transform\u00e9 en mod\u00e8le +cm_contentmodel.aspect.cm_templatable.description=Peut \u00eatre transform\u00e9 en mod\u00e8le cm_contentmodel.property.cm_template.title=Mod\u00e8le cm_contentmodel.property.cm_template.description=Mod\u00e8le cm_contentmodel.aspect.cm_complianceable.title=Possibilit\u00e9 de mise en conformit\u00e9 cm_contentmodel.aspect.cm_complianceable.description=Possibilit\u00e9 de mise en conformit\u00e9 -cm_contentmodel.property.cm_removeAfter.title=Supprimer Apr\u00e8s -cm_contentmodel.property.cm_removeAfter.description=Supprimer Apr\u00e8s +cm_contentmodel.property.cm_removeAfter.title=Supprimer apr\u00e8s +cm_contentmodel.property.cm_removeAfter.description=Supprimer apr\u00e8s cm_contentmodel.aspect.cm_ownable.title=Accessible en propri\u00e9t\u00e9 cm_contentmodel.aspect.cm_ownable.description=Accessible en propri\u00e9t\u00e9 @@ -115,8 +167,8 @@ cm_contentmodel.property.cm_owner.description=Propri\u00e9taire cm_contentmodel.aspect.cm_dublincore.title=Dublin Core cm_contentmodel.aspect.cm_dublincore.description=Dublin Core -cm_contentmodel.property.cm_publisher.title=\u00c9diteur -cm_contentmodel.property.cm_publisher.description=\u00c9diteur +cm_contentmodel.property.cm_publisher.title=Editeur +cm_contentmodel.property.cm_publisher.description=Editeur cm_contentmodel.property.cm_contributor.title=Contributeur cm_contentmodel.property.cm_contributor.description=Contributeur cm_contentmodel.property.cm_type.title=Type @@ -169,40 +221,40 @@ cm_contentmodel.aspect.cm_countable.description=Peut \u00eatre compt\u00e9 cm_contentmodel.property.cm_hits.title=D\u00e9compte cm_contentmodel.property.cm_hits.description=D\u00e9compte -cm_contentmodel.aspect.cm_copiedFrom.title=Copi\u00e9 Depuis -cm_contentmodel.aspect.cm_copiedFrom.description=Copi\u00e9 Depuis +cm_contentmodel.aspect.cm_copiedFrom.title=Copi\u00e9 depuis +cm_contentmodel.aspect.cm_copiedFrom.description=Copi\u00e9 depuis cm_contentmodel.property.cm_source.title=Origine cm_contentmodel.property.cm_source.description=Origine cm_contentmodel.aspect.cm_workingcopy.title=Copie de travail cm_contentmodel.aspect.cm_workingcopy.description=Copie de travail -cm_contentmodel.property.cm_workingCopyOwner.title=Propri\u00e9taire de la Copie de Travail -cm_contentmodel.property.cm_workingCopyOwner.description=Propri\u00e9taire de la Copie de Travail +cm_contentmodel.property.cm_workingCopyOwner.title=Propri\u00e9taire de la copie de travail +cm_contentmodel.property.cm_workingCopyOwner.description=Propri\u00e9taire de la copie de travail cm_contentmodel.aspect.cm_versionable.title=Versionnable cm_contentmodel.aspect.cm_versionable.description=Versionnable cm_contentmodel.property.cm_versionLabel.title=Version cm_contentmodel.property.cm_versionLabel.description=Version -cm_contentmodel.property.cm_autoVersion.title=Version Automatique -cm_contentmodel.property.cm_autoVersion.description=Version Automatique -cm_contentmodel.property.cm_initialVersion.title=Version Initiale -cm_contentmodel.property.cm_initialVersion.description=Version Initiale +cm_contentmodel.property.cm_autoVersion.title=Version automatique +cm_contentmodel.property.cm_autoVersion.description=Version automatique +cm_contentmodel.property.cm_initialVersion.title=Version initiale +cm_contentmodel.property.cm_initialVersion.description=Version initiale cm_contentmodel.aspect.cm_lockable.title=Verrouillable cm_contentmodel.aspect.cm_lockable.description=Verrouillable -cm_contentmodel.property.cm_lockOwner.title=Propri\u00e9taire du Verrou -cm_contentmodel.property.cm_lockOwner.description=Propri\u00e9taire du Verrou -cm_contentmodel.property.cm_lockType.title=Type de Verrou -cm_contentmodel.property.cm_lockType.description=Type de Verrou -cm_contentmodel.property.cm_expiryDate.title=Date d'Expiration -cm_contentmodel.property.cm_expiryDate.description=Date d'Expiration -cm_contentmodel.property.cm_lockIsDeep.title=Verrou Profond -cm_contentmodel.property.cm_lockIsDeep.description=Verrou Profond +cm_contentmodel.property.cm_lockOwner.title=Propri\u00e9taire du verrou +cm_contentmodel.property.cm_lockOwner.description=Propri\u00e9taire du verrou +cm_contentmodel.property.cm_lockType.title=Type de verrou +cm_contentmodel.property.cm_lockType.description=Type de verrou +cm_contentmodel.property.cm_expiryDate.title=Date d'expiration +cm_contentmodel.property.cm_expiryDate.description=Date d'expiration +cm_contentmodel.property.cm_lockIsDeep.title=Verrou profond +cm_contentmodel.property.cm_lockIsDeep.description=Verrou profond cm_contentmodel.aspect.cm_subscribable.title=Souscription possible cm_contentmodel.aspect.cm_subscribable.description=Souscription possible -cm_contentmodel.association.cm_subscribedBy.title=Souscrit Par -cm_contentmodel.association.cm_subscribedBy.description=Souscrit Par +cm_contentmodel.association.cm_subscribedBy.title=Souscrit par +cm_contentmodel.association.cm_subscribedBy.description=Souscrit par cm_contentmodel.aspect.cm_classifiable.title=Cat\u00e9gorisable cm_contentmodel.aspect.cm_classifiable.description=Cat\u00e9gorisable @@ -212,18 +264,18 @@ cm_contentmodel.aspect.cm_generalclassifiable.description=Cat\u00e9gorisable cm_contentmodel.property.cm_categories.title=Cat\u00e9gories cm_contentmodel.property.cm_categories.description=Cat\u00e9gories -cm_contentmodel.aspect.cm_taggable.title=Peut \u00eatre tagg\u00e9 -cm_contentmodel.aspect.cm_taggable.description=Peut \u00eatre tagg\u00e9 -cm_contentmodel.property.cm_taggable.title=\u00c9tiquettes -cm_contentmodel.property.cm_taggable.description=\u00c9tiquettes +cm_contentmodel.aspect.cm_taggable.title=Peut \u00eatre associ\u00e9 \u00e0 des tags +cm_contentmodel.aspect.cm_taggable.description=Peut \u00eatre associ\u00e9 \u00e0 des tags +cm_contentmodel.property.cm_taggable.title=Tags +cm_contentmodel.property.cm_taggable.description=Tags cm_contentmodel.aspect.cm_attachable.title=Pi\u00e8ces jointes possibles -cm_contentmodel.aspect.cm_attachable.description=Permet \u00e0 d'autres objets de l'entrepot d'\u00eatre attach\u00e9s +cm_contentmodel.aspect.cm_attachable.description=Permet \u00e0 d'autres objets de l'entrep\u00f4t d'\u00eatre attach\u00e9s cm_contentmodel.association.cm_attachments.title=El\u00e9ments attach\u00e9s -cm_contentmodel.association.cm_attachments.description=Objets de r\u00e9f\u00e9rentiel joints +cm_contentmodel.association.cm_attachments.description=Objets d'entrep\u00f4t joints -cm_contentmodel.aspect.cm_emailed.title=Envoy\u00e9 par mail -cm_contentmodel.aspect.cm_emailed.description=Envoy\u00e9 par mail +cm_contentmodel.aspect.cm_emailed.title=Envoy\u00e9 par e-mail +cm_contentmodel.aspect.cm_emailed.description=Envoy\u00e9 par e-mail cm_contentmodel.property.cm_originator.title=Exp\u00e9diteur cm_contentmodel.property.cm_originator.description=Exp\u00e9diteur cm_contentmodel.property.cm_addressee.title=Destinataire @@ -243,8 +295,8 @@ cm_contentmodel.property.cm_longitude.title=Longitude cm_contentmodel.property.cm_longitude.description=Longitude cm_contentmodel.aspect.exif_exif.title=EXIF -cm_contentmodel.aspect.exif_exif.description=Sous-ensemble des m\u00e9tadonn\u00e9es\u00a0EXIF standard -cm_contentmodel.property.exif_dateTimeOriginal.title=Date et Heure +cm_contentmodel.aspect.exif_exif.description=Sous-ensemble des m\u00e9tadonn\u00e9es EXIF standard +cm_contentmodel.property.exif_dateTimeOriginal.title=Date et heure cm_contentmodel.property.exif_dateTimeOriginal.description=Date et heure de g\u00e9n\u00e9ration de l'image originale cm_contentmodel.property.exif_pixelXDimension.title=Largeur de l'image cm_contentmodel.property.exif_pixelXDimension.description=Largeur de l'image, en pixels @@ -258,8 +310,8 @@ cm_contentmodel.property.exif_flash.title=Flash activ\u00e9 cm_contentmodel.property.exif_flash.description=Indique si le flash s'est d\u00e9clench\u00e9 lors de la prise de la photo cm_contentmodel.property.exif_focalLength.title=Longueur focale cm_contentmodel.property.exif_focalLength.description=Longueur focale de l'objectif, en millim\u00e8tres -cm_contentmodel.property.exif_isoSpeedRatings.title=Sensibilit\u00e9\u00a0ISO -cm_contentmodel.property.exif_isoSpeedRatings.description=Sensibilit\u00e9\u00a0ISO +cm_contentmodel.property.exif_isoSpeedRatings.title=Sensibilit\u00e9 ISO +cm_contentmodel.property.exif_isoSpeedRatings.description=Sensibilit\u00e9 ISO cm_contentmodel.property.exif_manufacturer.title=Fabricant de l'appareil photo cm_contentmodel.property.exif_manufacturer.description=Fabricant de l'appareil ayant r\u00e9alis\u00e9 la photo cm_contentmodel.property.exif_model.title=Mod\u00e8le d'appareil photo @@ -274,3 +326,4 @@ cm_contentmodel.property.exif_yResolution.title=R\u00e9solution verticale cm_contentmodel.property.exif_yResolution.description=R\u00e9solution verticale, en pixels par unit\u00e9 cm_contentmodel.property.exif_resolutionUnit.title=Unit\u00e9 de r\u00e9solution cm_contentmodel.property.exif_resolutionUnit.description=Unit\u00e9 utilis\u00e9e pour les r\u00e9solutions horizontale et verticale + diff --git a/config/alfresco/messages/content-model_it.properties b/config/alfresco/messages/content-model_it.properties index 887ff0501d..9a74247f84 100755 --- a/config/alfresco/messages/content-model_it.properties +++ b/config/alfresco/messages/content-model_it.properties @@ -45,9 +45,61 @@ cm_contentmodel.property.cm_middleName.title=Secondo nome cm_contentmodel.property.cm_middleName.description=Secondo nome della persona cm_contentmodel.property.cm_email.title=Indirizzo e-mail cm_contentmodel.property.cm_email.description=Indirizzo e-mail della persona -cm_contentmodel.property.cm_organizationId.title=Organizzazione -cm_contentmodel.property.cm_organizationId.description=Organizzazione della persona - +cm_contentmodel.property.cm_homeFolderProvider.title=Provider cartella homepage +cm_contentmodel.property.cm_homeFolderProvider.description=Provider cartella homepage +cm_contentmodel.property.cm_defaultHomeFolderPath.title=Percorso cartella homepage +cm_contentmodel.property.cm_defaultHomeFolderPath.description=Percorso alla cartella homepage della persona +cm_contentmodel.property.cm_presenceProvider.title=Provider Presense +cm_contentmodel.property.cm_presenceProvider.description=Provider Presense +cm_contentmodel.property.cm_presenceUsername.title=Nome utente Presense +cm_contentmodel.property.cm_presenceUsername.description=Nome utente Presense +cm_contentmodel.property.cm_jobtitle.title=Qualifica +cm_contentmodel.property.cm_jobtitle.description=Qualifica della persona +cm_contentmodel.property.cm_location.title=Localit\u00e0 +cm_contentmodel.property.cm_location.description=Localit\u00e0 della persona +cm_contentmodel.property.cm_persondescription.title=Sommario +cm_contentmodel.property.cm_persondescription.description=Sommario della persona +cm_contentmodel.property.cm_telephone.title=Telefono +cm_contentmodel.property.cm_telephone.description=Numero di telefono della persona +cm_contentmodel.property.cm_mobile.title=Cellulare +cm_contentmodel.property.cm_mobile.description=Numero di telefono cellulare della persona +cm_contentmodel.property.cm_organizationId.title=ID organizzazione +cm_contentmodel.property.cm_organizationId.description=ID organizzazione della persona +cm_contentmodel.property.cm_organization.title=Azienda +cm_contentmodel.property.cm_organization.description=Azienda della persona +cm_contentmodel.property.cm_companyaddress1.title=Indirizzo +cm_contentmodel.property.cm_companyaddress1.description=Riga 1 dell'indirizzo dell'azienda della persona +cm_contentmodel.property.cm_companyaddress2.title=Riga 2 indirizzo +cm_contentmodel.property.cm_companyaddress2.description=Riga 2 dell'indirizzo dell'azienda della persona +cm_contentmodel.property.cm_companyaddress3.title=Riga 3 indirizzo +cm_contentmodel.property.cm_companyaddress3.description=Riga 3 dell'indirizzo dell'azienda della persona +cm_contentmodel.property.cm_companypostcode.title=Codice postale +cm_contentmodel.property.cm_companypostcode.description=Codice postale dell'azienda della persona +cm_contentmodel.property.cm_companytelephone.title=Telefono +cm_contentmodel.property.cm_companytelephone.description=Numero di telefono dell'azienda della persona +cm_contentmodel.property.cm_companyfax.title=Fax +cm_contentmodel.property.cm_companyfax.description=Numero fax dell'azienda della persona +cm_contentmodel.property.cm_companyemail.title=E-mail +cm_contentmodel.property.cm_companyemail.description=Indirizzo e-mail dell'azienda della persona +cm_contentmodel.property.cm_skype.title=Skype +cm_contentmodel.property.cm_skype.description=ID utente Skype della persona +cm_contentmodel.property.cm_instantmsg.title=Messaggistica immediata +cm_contentmodel.property.cm_instantmsg.description=ID utente messaggistica immediata della persona +cm_contentmodel.property.cm_userStatus.title=Stato +cm_contentmodel.property.cm_userStatus.description=Stato corrente della persona +cm_contentmodel.property.cm_userStatusTime.title=Ora stato +cm_contentmodel.property.cm_userStatusTime.description=Ora ultimo aggiornamento dello stato della persona +cm_contentmodel.property.cm_googleusername.title=Nome utente Google +cm_contentmodel.property.cm_googleusername.description=Nome utente Google della persona +cm_contentmodel.property.cm_sizeCurrent.title=Uso +cm_contentmodel.property.cm_sizeCurrent.description=Quantit\u00e0 di spazio su disco utilizzata dalla persona +cm_contentmodel.property.cm_sizeQuota.title=Quota +cm_contentmodel.property.cm_sizeQuota.description=Quantit\u00e0 massima di spazio su disco utilizzabile dalla persona +cm_contentmodel.property.cm_emailFeedId.title=ID feed e-mail +cm_contentmodel.property.cm_emailFeedId.description=ID del feed e-mail della persona +cm_contentmodel.association.cm_avatar.title=Avatar +cm_contentmodel.association.cm_avatar.description=Immagine avatar della persona + cm_contentmodel.type.cm_category_root.title=Radice categorie cm_contentmodel.type.cm_category_root.description=Categoria radice cm_contentmodel.association.cm_categories.title=Categorie @@ -76,17 +128,17 @@ cm_contentmodel.property.cm_modified.description=Data dell'ultima modifica dell' cm_contentmodel.property.cm_modifier.title=Modificatore cm_contentmodel.property.cm_modifier.description=Utente che ha apportato l'ultima modifica all'elemento cm_contentmodel.property.cm_accessed.title=Data di ultimo accesso -cm_contentmodel.property.cm_accessed.description=Data dell'ultimo accesso all'elemento +cm_contentmodel.property.cm_accessed.description=Data dell'ultimo accesso all'elemento -cm_contentmodel.aspect.cm_author.title=Author -cm_contentmodel.aspect.cm_author.description=Author +cm_contentmodel.aspect.cm_author.title=Autore +cm_contentmodel.aspect.cm_author.description=Autore cm_contentmodel.property.cm_author.title=Autore cm_contentmodel.property.cm_author.description=Autore cm_contentmodel.aspect.cm_localizable.title=Localizable cm_contentmodel.aspect.cm_localizable.description=Localizable cm_contentmodel.property.cm_locale.title=Impostazioni locali -cm_contentmodel.property.cm_locale.description=Impostazioni locali +cm_contentmodel.property.cm_locale.description=Impostazioni locali cm_contentmodel.aspect.cm_translatable.title=Translatable cm_contentmodel.aspect.cm_translatable.description=Translatable @@ -98,13 +150,13 @@ cm_contentmodel.aspect.cm_transformable.description=Transformable cm_contentmodel.association.cm_formats.title=Formati cm_contentmodel.association.cm_formats.description=Elementi trasformati -cm_contentmodel.aspect.cm_templatable.title=Templatable -cm_contentmodel.aspect.cm_templatable.description=Templatable +cm_contentmodel.aspect.cm_templatable.title=Modellabile +cm_contentmodel.aspect.cm_templatable.description=Modellabile cm_contentmodel.property.cm_template.title=Modello cm_contentmodel.property.cm_template.description=Modello -cm_contentmodel.aspect.cm_complianceable.title=Complianceable -cm_contentmodel.aspect.cm_complianceable.description=Complianceable +cm_contentmodel.aspect.cm_complianceable.title=Conformabile +cm_contentmodel.aspect.cm_complianceable.description=Conformabile cm_contentmodel.property.cm_removeAfter.title=Rimuovi dopo cm_contentmodel.property.cm_removeAfter.description=Rimuovi dopo @@ -152,15 +204,15 @@ cm_contentmodel.aspect.cm_replacable.description=Replaceable cm_contentmodel.association.cm_replaces.title=Sostituzioni cm_contentmodel.association.cm_replaces.description=Sostituzioni -cm_contentmodel.aspect.cm_effectivity.title=Effectivity -cm_contentmodel.aspect.cm_effectivity.description=Effectivity +cm_contentmodel.aspect.cm_effectivity.title=Validit\u00e0 +cm_contentmodel.aspect.cm_effectivity.description=Validit\u00e0 cm_contentmodel.property.cm_from.title=Valido da cm_contentmodel.property.cm_from.description=Valido da cm_contentmodel.property.cm_to.title=Valido fino a cm_contentmodel.property.cm_to.description=Valido fino a -cm_contentmodel.aspect.cm_summarizable.title=Summarizable -cm_contentmodel.aspect.cm_summarizable.description=Summarizable +cm_contentmodel.aspect.cm_summarizable.title=Riepilogabile +cm_contentmodel.aspect.cm_summarizable.description=Riepilogabile cm_contentmodel.property.cm_summary.title=Sommario cm_contentmodel.property.cm_summary.description=Sommario @@ -174,13 +226,13 @@ cm_contentmodel.aspect.cm_copiedFrom.description=Copied From cm_contentmodel.property.cm_source.title=Fonte cm_contentmodel.property.cm_source.description=Fonte -cm_contentmodel.aspect.cm_workingcopy.title=Working Copy -cm_contentmodel.aspect.cm_workingcopy.description=Working Copy +cm_contentmodel.aspect.cm_workingcopy.title=Copia di lavoro +cm_contentmodel.aspect.cm_workingcopy.description=Copia di lavoro cm_contentmodel.property.cm_workingCopyOwner.title=Proprietario della copia di lavoro cm_contentmodel.property.cm_workingCopyOwner.description=Proprietario della copia di lavoro -cm_contentmodel.aspect.cm_versionable.title=Versionable -cm_contentmodel.aspect.cm_versionable.description=Versionable +cm_contentmodel.aspect.cm_versionable.title=Gestibile con versioni +cm_contentmodel.aspect.cm_versionable.description=Gestibile con versioni cm_contentmodel.property.cm_versionLabel.title=Etichetta della versione cm_contentmodel.property.cm_versionLabel.description=Etichetta della versione cm_contentmodel.property.cm_autoVersion.title=Versione automatica @@ -188,8 +240,8 @@ cm_contentmodel.property.cm_autoVersion.description=Versione automatica cm_contentmodel.property.cm_initialVersion.title=Versione iniziale cm_contentmodel.property.cm_initialVersion.description=Versione iniziale -cm_contentmodel.aspect.cm_lockable.title=Lockable -cm_contentmodel.aspect.cm_lockable.description=Lockable +cm_contentmodel.aspect.cm_lockable.title=Bloccabile +cm_contentmodel.aspect.cm_lockable.description=Bloccabile cm_contentmodel.property.cm_lockOwner.title=Proprietario del blocco cm_contentmodel.property.cm_lockOwner.description=Proprietario del blocco cm_contentmodel.property.cm_lockType.title=Tipo di blocco @@ -204,26 +256,26 @@ cm_contentmodel.aspect.cm_subscribable.description=Subscribable cm_contentmodel.association.cm_subscribedBy.title=Sottoscritto da cm_contentmodel.association.cm_subscribedBy.description=Sottoscritto da -cm_contentmodel.aspect.cm_classifiable.title=Classifiable -cm_contentmodel.aspect.cm_classifiable.description=Classifiable +cm_contentmodel.aspect.cm_classifiable.title=Classificabile +cm_contentmodel.aspect.cm_classifiable.description=Classificabile -cm_contentmodel.aspect.cm_generalclassifiable.title=Classifiable -cm_contentmodel.aspect.cm_generalclassifiable.description=Classifiable +cm_contentmodel.aspect.cm_generalclassifiable.title=Classificabile +cm_contentmodel.aspect.cm_generalclassifiable.description=Classificabile cm_contentmodel.property.cm_categories.title=Categorie cm_contentmodel.property.cm_categories.description=Categorie -cm_contentmodel.aspect.cm_taggable.title=Taggable -cm_contentmodel.aspect.cm_taggable.description=Taggable +cm_contentmodel.aspect.cm_taggable.title=Etichettabile +cm_contentmodel.aspect.cm_taggable.description=Etichettabile cm_contentmodel.property.cm_taggable.title=Tag cm_contentmodel.property.cm_taggable.description=Tag -cm_contentmodel.aspect.cm_attachable.title=Attachable +cm_contentmodel.aspect.cm_attachable.title=Associabile cm_contentmodel.aspect.cm_attachable.description=Consente di allegare altri oggetti del repository cm_contentmodel.association.cm_attachments.title=Allegati cm_contentmodel.association.cm_attachments.description=Oggetti del repository allegati -cm_contentmodel.aspect.cm_emailed.title=Emailed -cm_contentmodel.aspect.cm_emailed.description=Emailed +cm_contentmodel.aspect.cm_emailed.title=Inviato via e-mail +cm_contentmodel.aspect.cm_emailed.description=Inviato via e-mail cm_contentmodel.property.cm_originator.title=Originatore cm_contentmodel.property.cm_originator.description=Originatore cm_contentmodel.property.cm_addressee.title=Destinatario @@ -235,8 +287,8 @@ cm_contentmodel.property.cm_subjectline.description=Oggetto cm_contentmodel.property.cm_sentdate.title=Data di invio cm_contentmodel.property.cm_sentdate.description=Data di invio -cm_contentmodel.aspect.cm_geographic.title=Geographic -cm_contentmodel.aspect.cm_geographic.description=Geographic +cm_contentmodel.aspect.cm_geographic.title=Geografico +cm_contentmodel.aspect.cm_geographic.description=Geografico cm_contentmodel.property.cm_latitude.title=Latitudine cm_contentmodel.property.cm_latitude.description=Latitudine cm_contentmodel.property.cm_longitude.title=Longitudine @@ -274,3 +326,4 @@ cm_contentmodel.property.exif_yResolution.title=Risoluzione verticale cm_contentmodel.property.exif_yResolution.description=Risoluzione verticale in pixel per unit\u00e0 cm_contentmodel.property.exif_resolutionUnit.title=Unit\u00e0 di risoluzione cm_contentmodel.property.exif_resolutionUnit.description=Unit\u00e0 utilizzata per la risoluzione orizzontale e verticale + diff --git a/config/alfresco/messages/content-model_ja.properties b/config/alfresco/messages/content-model_ja.properties new file mode 100755 index 0000000000..e19d86789e --- /dev/null +++ b/config/alfresco/messages/content-model_ja.properties @@ -0,0 +1,329 @@ +# Display labels for Content Domain Model + +cm_contentmodel.description=Alfresco Content Domain Model + +cm_contentmodel.type.cm_object.title=\u30aa\u30d6\u30b8\u30a7\u30af\u30c8 +cm_contentmodel.type.cm_object.description=\u30d9\u30fc\u30b9\u30fb\u30b3\u30f3\u30c6\u30f3\u30c4\u30fb\u30c9\u30e1\u30a4\u30f3\u30fb\u30aa\u30d6\u30b8\u30a7\u30af\u30c8 +cm_contentmodel.property.cm_name.title=\u540d\u524d +cm_contentmodel.property.cm_name.description=\u540d\u524d + +cm_contentmodel.type.cm_folder.title=\u30d5\u30a9\u30eb\u30c0 +cm_contentmodel.type.cm_folder.description=\u30d5\u30a9\u30eb\u30c0 +cm_contentmodel.property.cm_orderedchildren.title=\u9806\u5e8f\u4ed8\u3051\u3089\u308c\u305f\u5b50 +cm_contentmodel.property.cm_orderedchildren.description=\u30d5\u30a9\u30eb\u30c0\u306e\u5b50\u304c\u6b63\u3057\u304f\u4e26\u3079\u3089\u308c\u305f\u304b\u3069\u3046\u304b\u3092\u793a\u3057\u307e\u3059 +cm_contentmodel.association.cm_contains.title=\u542b\u3080 +cm_contentmodel.association.cm_contains.description=\u542b\u3080 + +cm_contentmodel.type.cm_content.title=\u30b3\u30f3\u30c6\u30f3\u30c4 +cm_contentmodel.type.cm_content.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u57fa\u672c\u30aa\u30d6\u30b8\u30a7\u30af\u30c8 +cm_contentmodel.property.cm_content.title=\u30b3\u30f3\u30c6\u30f3\u30c4 +cm_contentmodel.property.cm_content.description=\u30b3\u30f3\u30c6\u30f3\u30c4 + +cm_contentmodel.type.cm_linkfile.title=\u30d5\u30a1\u30a4\u30eb\u30ea\u30f3\u30af +cm_contentmodel.type.cm_linkfile.description=\u4ed6\u306e\u30d5\u30a1\u30a4\u30eb\u3078\u306e\u30ea\u30f3\u30af +cm_contentmodel.property.cm_path.title=\u30ea\u30f3\u30af\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9 +cm_contentmodel.property.cm_path.description=\u30ea\u30f3\u30af\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb\u3078\u306e\u30d1\u30b9 + +cm_contentmodel.type.cm_savedquery.title=\u4fdd\u5b58\u6e08\u554f\u5408\u305b +cm_contentmodel.type.cm_savedquery.description=\u4fdd\u5b58\u6e08\u554f\u5408\u305b + +cm_contentmodel.type.cm_systemfolder.title=\u30b7\u30b9\u30c6\u30e0\u30fb\u30d5\u30a9\u30eb\u30c0 +cm_contentmodel.type.cm_systemfolder.description=\u30b7\u30b9\u30c6\u30e0\u30fb\u30ec\u30d9\u30eb\u306e\u30a2\u30a4\u30c6\u30e0\u3092\u542b\u3080\u30d5\u30a9\u30eb\u30c0 + +cm_contentmodel.type.cm_person.title=\u4eba +cm_contentmodel.type.cm_person.description=\u4eba + +cm_contentmodel.property.cm_userName.title=\u30e6\u30fc\u30b6\u540d +cm_contentmodel.property.cm_userName.description=\u4eba\u306e\u30e6\u30fc\u30b6\u540d +cm_contentmodel.property.cm_homeFolder.title=\u30db\u30fc\u30e0\u30fb\u30d5\u30a9\u30eb\u30c0 +cm_contentmodel.property.cm_homeFolder.description=\u4eba\u306e\u30db\u30fc\u30e0\u30fb\u30d5\u30a9\u30eb\u30c0 +cm_contentmodel.property.cm_firstName.title=\u540d +cm_contentmodel.property.cm_firstName.description=\u4eba\u306e\u540d +cm_contentmodel.property.cm_lastName.title=\u59d3 +cm_contentmodel.property.cm_lastName.description=\u4eba\u306e\u59d3 +cm_contentmodel.property.cm_middleName.title=\u30df\u30c9\u30eb\u30cd\u30fc\u30e0 +cm_contentmodel.property.cm_middleName.description=\u4eba\u306e\u30df\u30c9\u30eb\u30fb\u30cd\u30fc\u30e0 +cm_contentmodel.property.cm_email.title=E\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9 +cm_contentmodel.property.cm_email.description=\u4eba\u306eE\u30e1\u30fc\u30eb\u30fb\u30a2\u30c9\u30ec\u30b9 +cm_contentmodel.property.cm_homeFolderProvider.title=\u30db\u30fc\u30e0\u30fb\u30d5\u30a9\u30eb\u30c0\u306e\u30d7\u30ed\u30d0\u30a4\u30c0 +cm_contentmodel.property.cm_homeFolderProvider.description=\u30db\u30fc\u30e0\u30fb\u30d5\u30a9\u30eb\u30c0\u306e\u30d7\u30ed\u30d0\u30a4\u30c0 +cm_contentmodel.property.cm_defaultHomeFolderPath.title=\u30db\u30fc\u30e0\u30fb\u30d5\u30a9\u30eb\u30c0\u306e\u30d1\u30b9 +cm_contentmodel.property.cm_defaultHomeFolderPath.description=\u4eba\u306e\u30db\u30fc\u30e0\u30fb\u30d5\u30a9\u30eb\u30c0\u3078\u306e\u30d1\u30b9 +cm_contentmodel.property.cm_presenceProvider.title=\u30d7\u30ec\u30bc\u30f3\u30b9\u30fb\u30d7\u30ed\u30d0\u30a4\u30c0 +cm_contentmodel.property.cm_presenceProvider.description=\u30d7\u30ec\u30bc\u30f3\u30b9\u30fb\u30d7\u30ed\u30d0\u30a4\u30c0 +cm_contentmodel.property.cm_presenceUsername.title=\u30d7\u30ec\u30bc\u30f3\u30b9\u30fb\u30e6\u30fc\u30b6\u540d +cm_contentmodel.property.cm_presenceUsername.description=\u30d7\u30ec\u30bc\u30f3\u30b9\u30fb\u30e6\u30fc\u30b6\u540d +cm_contentmodel.property.cm_jobtitle.title=\u5f79\u8077\u540d +cm_contentmodel.property.cm_jobtitle.description=\u4eba\u306e\u5f79\u8077 +cm_contentmodel.property.cm_location.title=\u5834\u6240 +cm_contentmodel.property.cm_location.description=\u4eba\u306e\u5834\u6240 +cm_contentmodel.property.cm_persondescription.title=\u6982\u8981 +cm_contentmodel.property.cm_persondescription.description=\u4eba\u306e\u6982\u8981 +cm_contentmodel.property.cm_telephone.title=\u96fb\u8a71 +cm_contentmodel.property.cm_telephone.description=\u4eba\u306e\u96fb\u8a71\u756a\u53f7 +cm_contentmodel.property.cm_mobile.title=\u643a\u5e2f +cm_contentmodel.property.cm_mobile.description=\u4eba\u306e\u643a\u5e2f\u96fb\u8a71\u756a\u53f7 +cm_contentmodel.property.cm_organizationId.title=\u7d44\u7e54ID +cm_contentmodel.property.cm_organizationId.description=\u4eba\u306e\u7d44\u7e54ID +cm_contentmodel.property.cm_organization.title=\u4f1a\u793e +cm_contentmodel.property.cm_organization.description=\u4eba\u306e\u4f1a\u793e +cm_contentmodel.property.cm_companyaddress1.title=\u4f4f\u6240 +cm_contentmodel.property.cm_companyaddress1.description=\u4eba\u306e\u4f1a\u793e\u4f4f\u6240\u306e1\u884c\u76ee +cm_contentmodel.property.cm_companyaddress2.title=\u4f4f\u6240\u306e2\u884c\u76ee +cm_contentmodel.property.cm_companyaddress2.description=\u4eba\u306e\u4f1a\u793e\u4f4f\u6240\u306e2\u884c\u76ee +cm_contentmodel.property.cm_companyaddress3.title=\u4f4f\u6240\u306e3\u884c\u76ee +cm_contentmodel.property.cm_companyaddress3.description=\u4eba\u306e\u4f1a\u793e\u4f4f\u6240\u306e3\u884c\u76ee +cm_contentmodel.property.cm_companypostcode.title=\u90f5\u4fbf\u756a\u53f7 +cm_contentmodel.property.cm_companypostcode.description=\u4eba\u306e\u4f1a\u793e\u306e\u90f5\u4fbf\u756a\u53f7 +cm_contentmodel.property.cm_companytelephone.title=\u96fb\u8a71 +cm_contentmodel.property.cm_companytelephone.description=\u4eba\u306e\u4f1a\u793e\u306e\u96fb\u8a71\u756a\u53f7 +cm_contentmodel.property.cm_companyfax.title=\u30d5\u30a1\u30c3\u30af\u30b9 +cm_contentmodel.property.cm_companyfax.description=\u4eba\u306e\u4f1a\u793e\u306e\u30d5\u30a1\u30c3\u30af\u30b9\u756a\u53f7 +cm_contentmodel.property.cm_companyemail.title=E\u30e1\u30fc\u30eb +cm_contentmodel.property.cm_companyemail.description=\u4eba\u306e\u4f1a\u793e\u306eE\u30e1\u30fc\u30eb\u30fb\u30a2\u30c9\u30ec\u30b9 +cm_contentmodel.property.cm_skype.title=Skype +cm_contentmodel.property.cm_skype.description=\u4eba\u306eSkype\u30e6\u30fc\u30b6ID +cm_contentmodel.property.cm_instantmsg.title=IM +cm_contentmodel.property.cm_instantmsg.description=\u4eba\u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30c8\u30fb\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u30e6\u30fc\u30b6ID +cm_contentmodel.property.cm_userStatus.title=\u30b9\u30c6\u30fc\u30bf\u30b9 +cm_contentmodel.property.cm_userStatus.description=\u4eba\u306e\u73fe\u5728\u306e\u30b9\u30c6\u30fc\u30bf\u30b9 +cm_contentmodel.property.cm_userStatusTime.title=\u30b9\u30c6\u30fc\u30bf\u30b9\u6642\u9593 +cm_contentmodel.property.cm_userStatusTime.description=\u4eba\u306e\u30b9\u30c6\u30fc\u30bf\u30b9\u304c\u6700\u5f8c\u306b\u66f4\u65b0\u3055\u308c\u305f\u6642\u9593 +cm_contentmodel.property.cm_googleusername.title=Google\u30e6\u30fc\u30b6\u540d +cm_contentmodel.property.cm_googleusername.description=\u4eba\u306eGoogle\u30e6\u30fc\u30b6\u540d +cm_contentmodel.property.cm_sizeCurrent.title=\u4f7f\u7528\u91cf +cm_contentmodel.property.cm_sizeCurrent.description=\u4eba\u304c\u4f7f\u7528\u3057\u3066\u3044\u308b\u30c7\u30a3\u30b9\u30af\u9818\u57df\u306e\u91cf +cm_contentmodel.property.cm_sizeQuota.title=\u30af\u30a9\u30fc\u30bf +cm_contentmodel.property.cm_sizeQuota.description=\u4eba\u304c\u4f7f\u7528\u3067\u304d\u308b\u6700\u5927\u30c7\u30a3\u30b9\u30af\u9818\u57df +cm_contentmodel.property.cm_emailFeedId.title=E\u30e1\u30fc\u30eb\u30fb\u30d5\u30a3\u30fc\u30c9ID +cm_contentmodel.property.cm_emailFeedId.description=\u4eba\u306eE\u30e1\u30fc\u30eb\u30fb\u30d5\u30a3\u30fc\u30c9ID\u306eID +cm_contentmodel.association.cm_avatar.title=\u30a2\u30d0\u30bf\u30fc +cm_contentmodel.association.cm_avatar.description=\u4eba\u306e\u30a2\u30d0\u30bf\u30fc\u753b\u50cf + +cm_contentmodel.type.cm_category_root.title=\u30ab\u30c6\u30b4\u30ea\u30fb\u30eb\u30fc\u30c8 +cm_contentmodel.type.cm_category_root.description=\u30eb\u30fc\u30c8\u30fb\u30ab\u30c6\u30b4\u30ea +cm_contentmodel.association.cm_categories.title=\u30ab\u30c6\u30b4\u30ea +cm_contentmodel.association.cm_categories.description=\u30ab\u30c6\u30b4\u30ea\u30fb\u30eb\u30fc\u30c8\u306b\u542b\u307e\u308c\u308b\u30ab\u30c6\u30b4\u30ea + +cm_contentmodel.type.cm_category.title=\u30ab\u30c6\u30b4\u30ea +cm_contentmodel.type.cm_category.description=\u30ab\u30c6\u30b4\u30ea +cm_contentmodel.association.cm_subcategories.title=\u30ab\u30c6\u30b4\u30ea +cm_contentmodel.association.cm_subcategories.description=\u30ab\u30c6\u30b4\u30ea\u306b\u542b\u307e\u308c\u308b\u30b5\u30d6\u30ab\u30c6\u30b4\u30ea + +cm_contentmodel.aspect.cm_titled.title=\u30bf\u30a4\u30c8\u30eb\u306e\u3042\u308b +cm_contentmodel.aspect.cm_titled.description=\u30bf\u30a4\u30c8\u30eb\u306e\u3042\u308b +cm_contentmodel.property.cm_title.title=\u30bf\u30a4\u30c8\u30eb +cm_contentmodel.property.cm_title.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u30fb\u30bf\u30a4\u30c8\u30eb +cm_contentmodel.property.cm_description.title=\u8aac\u660e +cm_contentmodel.property.cm_description.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u8aac\u660e + +cm_contentmodel.aspect.cm_auditable.title=\u76e3\u67fb\u53ef\u80fd +cm_contentmodel.aspect.cm_auditable.description=\u76e3\u67fb\u53ef\u80fd +cm_contentmodel.property.cm_created.title=\u4f5c\u6210\u65e5 +cm_contentmodel.property.cm_created.description=\u4f5c\u6210\u65e5 +cm_contentmodel.property.cm_creator.title=\u4f5c\u6210\u8005 +cm_contentmodel.property.cm_creator.description=\u3053\u306e\u30a2\u30a4\u30c6\u30e0\u306e\u4f5c\u6210\u8005 +cm_contentmodel.property.cm_modified.title=\u5909\u66f4\u65e5 +cm_contentmodel.property.cm_modified.description=\u3053\u306e\u30a2\u30a4\u30c6\u30e0\u306e\u6700\u7d42\u5909\u66f4\u65e5 +cm_contentmodel.property.cm_modifier.title=\u4fee\u6b63\u8005 +cm_contentmodel.property.cm_modifier.description=\u3053\u306e\u30a2\u30a4\u30c6\u30e0\u306e\u6700\u7d42\u5909\u66f4\u8005 +cm_contentmodel.property.cm_accessed.title=\u6700\u5f8c\u30a2\u30af\u30bb\u30b9\u65e5 +cm_contentmodel.property.cm_accessed.description=\u3053\u306e\u30a2\u30a4\u30c6\u30e0\u3078\u306e\u6700\u7d42\u30a2\u30af\u30bb\u30b9\u65e5 + +cm_contentmodel.aspect.cm_author.title=\u4f5c\u6210\u8005 +cm_contentmodel.aspect.cm_author.description=\u4f5c\u6210\u8005 +cm_contentmodel.property.cm_author.title=\u4f5c\u6210\u8005 +cm_contentmodel.property.cm_author.description=\u4f5c\u6210\u8005 + +cm_contentmodel.aspect.cm_localizable.title=\u30ed\u30fc\u30ab\u30e9\u30a4\u30ba\u53ef\u80fd +cm_contentmodel.aspect.cm_localizable.description=\u30ed\u30fc\u30ab\u30e9\u30a4\u30ba\u53ef\u80fd +cm_contentmodel.property.cm_locale.title=\u5730\u57df +cm_contentmodel.property.cm_locale.description=\u5730\u57df + +cm_contentmodel.aspect.cm_translatable.title=\u7ffb\u8a33\u53ef\u80fd +cm_contentmodel.aspect.cm_translatable.description=\u7ffb\u8a33\u53ef\u80fd +cm_contentmodel.association.cm_translations.title=\u7ffb\u8a33 +cm_contentmodel.association.cm_translations.description=\u7ffb\u8a33 + +cm_contentmodel.aspect.cm_transformable.title=\u5909\u63db\u53ef\u80fd +cm_contentmodel.aspect.cm_transformable.description=\u5909\u63db\u53ef\u80fd +cm_contentmodel.association.cm_formats.title=\u30d5\u30a9\u30fc\u30de\u30c3\u30c8 +cm_contentmodel.association.cm_formats.description=\u5909\u63db\u6e08\u30a2\u30a4\u30c6\u30e0 + +cm_contentmodel.aspect.cm_templatable.title=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u5316\u5bfe\u8c61 +cm_contentmodel.aspect.cm_templatable.description=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u5316\u5bfe\u8c61 +cm_contentmodel.property.cm_template.title=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +cm_contentmodel.property.cm_template.description=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 + +cm_contentmodel.aspect.cm_complianceable.title=\u30b3\u30f3\u30d7\u30e9\u30a4\u30a2\u30f3\u30b9\u5bfe\u8c61 +cm_contentmodel.aspect.cm_complianceable.description=\u30b3\u30f3\u30d7\u30e9\u30a4\u30a2\u30f3\u30b9\u5bfe\u8c61 +cm_contentmodel.property.cm_removeAfter.title=\u305d\u306e\u5f8c\u53d6\u308a\u9664\u304f +cm_contentmodel.property.cm_removeAfter.description=\u305d\u306e\u5f8c\u53d6\u308a\u9664\u304f + +cm_contentmodel.aspect.cm_ownable.title=\u6240\u6709\u53ef\u80fd +cm_contentmodel.aspect.cm_ownable.description=\u6240\u6709\u53ef\u80fd +cm_contentmodel.property.cm_owner.title=\u6240\u6709\u8005 +cm_contentmodel.property.cm_owner.description=\u6240\u6709\u8005 + +cm_contentmodel.aspect.cm_dublincore.title=\u30c0\u30d6\u30ea\u30f3\u30b3\u30a2\u60c5\u5831\u4ed8\u304d +cm_contentmodel.aspect.cm_dublincore.description=\u30c0\u30d6\u30ea\u30f3\u30b3\u30a2\u60c5\u5831\u4ed8\u304d +cm_contentmodel.property.cm_publisher.title=\u767a\u884c\u8005 +cm_contentmodel.property.cm_publisher.description=\u767a\u884c\u8005 +cm_contentmodel.property.cm_contributor.title=\u30b3\u30f3\u30c8\u30ea\u30d3\u30e5\u30fc\u30bf +cm_contentmodel.property.cm_contributor.description=\u30b3\u30f3\u30c8\u30ea\u30d3\u30e5\u30fc\u30bf +cm_contentmodel.property.cm_type.title=\u30bf\u30a4\u30d7 +cm_contentmodel.property.cm_type.description=\u30bf\u30a4\u30d7 +cm_contentmodel.property.cm_identifier.title=ID +cm_contentmodel.property.cm_identifier.description=ID +cm_contentmodel.property.cm_dcsource.title=\u30bd\u30fc\u30b9 +cm_contentmodel.property.cm_dcsource.description=\u30bd\u30fc\u30b9 +cm_contentmodel.property.cm_coverage.title=\u30ab\u30d0\u30ec\u30c3\u30b8 +cm_contentmodel.property.cm_coverage.description=\u30ab\u30d0\u30ec\u30c3\u30b8 +cm_contentmodel.property.cm_rights.title=\u6a29\u5229 +cm_contentmodel.property.cm_rights.description=\u6a29\u5229 +cm_contentmodel.property.cm_subject.title=\u4ef6\u540d +cm_contentmodel.property.cm_subject.description=\u4ef6\u540d + +cm_contentmodel.aspect.cm_basable.title=\u30d9\u30fc\u30b9\u5316\u53ef\u80fd +cm_contentmodel.aspect.cm_basable.description=\u30d9\u30fc\u30b9\u5316\u53ef\u80fd +cm_contentmodel.association.cm_basis.title=\u57fa\u90e8 +cm_contentmodel.association.cm_basis.description=\u57fa\u90e8 + +cm_contentmodel.aspect.cm_partable.title=\u30d1\u30fc\u30c4\u5316\u53ef\u80fd +cm_contentmodel.aspect.cm_partable.description=\u30d1\u30fc\u30c4\u5316\u53ef\u80fd +cm_contentmodel.association.cm_parts.title=\u30d1\u30fc\u30c4 +cm_contentmodel.association.cm_parts.description=\u30d1\u30fc\u30c4 + +cm_contentmodel.aspect.cm_referencing.title=\u53c2\u7167\u306e +cm_contentmodel.aspect.cm_referencing.description=\u53c2\u7167\u306e +cm_contentmodel.association.cm_references.title=\u53c2\u7167 +cm_contentmodel.association.cm_references.description=\u53c2\u7167 + +cm_contentmodel.aspect.cm_replacable.title=\u7f6e\u63db\u53ef\u80fd +cm_contentmodel.aspect.cm_replacable.description=\u7f6e\u63db\u53ef\u80fd +cm_contentmodel.association.cm_replaces.title=\u7f6e\u63db +cm_contentmodel.association.cm_replaces.description=\u7f6e\u63db + +cm_contentmodel.aspect.cm_effectivity.title=\u6709\u52b9\u671f\u9650\u8a2d\u5b9a\u5bfe\u8c61 +cm_contentmodel.aspect.cm_effectivity.description=\u6709\u52b9\u671f\u9650\u8a2d\u5b9a\u5bfe\u8c61 +cm_contentmodel.property.cm_from.title=\u6709\u52b9\u671f\u9650\u958b\u59cb\u65e5 +cm_contentmodel.property.cm_from.description=\u6709\u52b9\u671f\u9650\u958b\u59cb\u65e5 +cm_contentmodel.property.cm_to.title=\u6709\u52b9\u671f\u9650\u7d42\u4e86\u65e5 +cm_contentmodel.property.cm_to.description=\u6709\u52b9\u671f\u9650\u7d42\u4e86\u65e5 + +cm_contentmodel.aspect.cm_summarizable.title=\u8981\u7d04\u5bfe\u8c61 +cm_contentmodel.aspect.cm_summarizable.description=\u8981\u7d04\u5bfe\u8c61 +cm_contentmodel.property.cm_summary.title=\u6982\u8981 +cm_contentmodel.property.cm_summary.description=\u6982\u8981 + +cm_contentmodel.aspect.cm_countable.title=\u53ef\u7b97\u306e +cm_contentmodel.aspect.cm_countable.description=\u53ef\u7b97\u306e +cm_contentmodel.property.cm_hits.title=\u30d2\u30c3\u30c8 +cm_contentmodel.property.cm_hits.description=\u30d2\u30c3\u30c8 + +cm_contentmodel.aspect.cm_copiedFrom.title=\u30b3\u30d4\u30fc\u5143 +cm_contentmodel.aspect.cm_copiedFrom.description=\u30b3\u30d4\u30fc\u5143 +cm_contentmodel.property.cm_source.title=\u30bd\u30fc\u30b9 +cm_contentmodel.property.cm_source.description=\u30bd\u30fc\u30b9 + +cm_contentmodel.aspect.cm_workingcopy.title=\u4f5c\u696d\u7528\u30b3\u30d4\u30fc +cm_contentmodel.aspect.cm_workingcopy.description=\u4f5c\u696d\u7528\u30b3\u30d4\u30fc +cm_contentmodel.property.cm_workingCopyOwner.title=\u4f5c\u696d\u7528\u30b3\u30d4\u30fc\u6240\u6709\u8005 +cm_contentmodel.property.cm_workingCopyOwner.description=\u4f5c\u696d\u7528\u30b3\u30d4\u30fc\u6240\u6709\u8005 + +cm_contentmodel.aspect.cm_versionable.title=\u30d0\u30fc\u30b8\u30e7\u30f3\u7ba1\u7406\u5bfe\u8c61 +cm_contentmodel.aspect.cm_versionable.description=\u30d0\u30fc\u30b8\u30e7\u30f3\u7ba1\u7406\u5bfe\u8c61 +cm_contentmodel.property.cm_versionLabel.title=\u30d0\u30fc\u30b8\u30e7\u30f3\u30e9\u30d9\u30eb +cm_contentmodel.property.cm_versionLabel.description=\u30d0\u30fc\u30b8\u30e7\u30f3\u30e9\u30d9\u30eb +cm_contentmodel.property.cm_autoVersion.title=\u81ea\u52d5\u30d0\u30fc\u30b8\u30e7\u30f3 +cm_contentmodel.property.cm_autoVersion.description=\u81ea\u52d5\u30d0\u30fc\u30b8\u30e7\u30f3 +cm_contentmodel.property.cm_initialVersion.title=\u521d\u671f\u30d0\u30fc\u30b8\u30e7\u30f3 +cm_contentmodel.property.cm_initialVersion.description=\u521d\u671f\u30d0\u30fc\u30b8\u30e7\u30f3 + +cm_contentmodel.aspect.cm_lockable.title=\u30ed\u30c3\u30af\u53ef\u80fd +cm_contentmodel.aspect.cm_lockable.description=\u30ed\u30c3\u30af\u53ef\u80fd +cm_contentmodel.property.cm_lockOwner.title=\u30ed\u30c3\u30af\u6240\u6709\u8005 +cm_contentmodel.property.cm_lockOwner.description=\u30ed\u30c3\u30af\u6240\u6709\u8005 +cm_contentmodel.property.cm_lockType.title=\u30ed\u30c3\u30af\u30bf\u30a4\u30d7 +cm_contentmodel.property.cm_lockType.description=\u30ed\u30c3\u30af\u30bf\u30a4\u30d7 +cm_contentmodel.property.cm_expiryDate.title=\u6709\u52b9\u671f\u9650 +cm_contentmodel.property.cm_expiryDate.description=\u6709\u52b9\u671f\u9650 +cm_contentmodel.property.cm_lockIsDeep.title=\u30c7\u30a3\u30fc\u30d7\u30ed\u30c3\u30af +cm_contentmodel.property.cm_lockIsDeep.description=\u30c7\u30a3\u30fc\u30d7\u30ed\u30c3\u30af + +cm_contentmodel.aspect.cm_subscribable.title=\u8cfc\u8aad\u53ef\u80fd +cm_contentmodel.aspect.cm_subscribable.description=\u8cfc\u8aad\u53ef\u80fd +cm_contentmodel.association.cm_subscribedBy.title=\u8cfc\u8aad\u8005 +cm_contentmodel.association.cm_subscribedBy.description=\u8cfc\u8aad\u8005 + +cm_contentmodel.aspect.cm_classifiable.title=\u5206\u985e\u5bfe\u8c61 +cm_contentmodel.aspect.cm_classifiable.description=\u5206\u985e\u5bfe\u8c61 + +cm_contentmodel.aspect.cm_generalclassifiable.title=\u5206\u985e\u5bfe\u8c61 +cm_contentmodel.aspect.cm_generalclassifiable.description=\u5206\u985e\u5bfe\u8c61 +cm_contentmodel.property.cm_categories.title=\u30ab\u30c6\u30b4\u30ea +cm_contentmodel.property.cm_categories.description=\u30ab\u30c6\u30b4\u30ea + +cm_contentmodel.aspect.cm_taggable.title=\u30bf\u30b0\u4ed8\u3051\u5bfe\u8c61 +cm_contentmodel.aspect.cm_taggable.description=\u30bf\u30b0\u4ed8\u3051\u5bfe\u8c61 +cm_contentmodel.property.cm_taggable.title=\u30bf\u30b0 +cm_contentmodel.property.cm_taggable.description=\u30bf\u30b0 + +cm_contentmodel.aspect.cm_attachable.title=\u6dfb\u4ed8\u53ef\u80fd +cm_contentmodel.aspect.cm_attachable.description=\u4ed6\u306e\u30ea\u30dd\u30b8\u30c8\u30ea\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u3092\u6dfb\u4ed8\u53ef\u80fd\u3067\u3059\u3002 +cm_contentmodel.association.cm_attachments.title=\u6dfb\u4ed8 +cm_contentmodel.association.cm_attachments.description=\u6dfb\u4ed8\u3055\u308c\u305f\u30ea\u30dd\u30b8\u30c8\u30ea\u30aa\u30d6\u30b8\u30a7\u30af\u30c8 + +cm_contentmodel.aspect.cm_emailed.title=E\u30e1\u30fc\u30eb\u9001\u4fe1\u5bfe\u8c61 +cm_contentmodel.aspect.cm_emailed.description=E\u30e1\u30fc\u30eb\u9001\u4fe1\u5bfe\u8c61 +cm_contentmodel.property.cm_originator.title=\u767a\u4fe1\u5143 +cm_contentmodel.property.cm_originator.description=\u767a\u4fe1\u5143 +cm_contentmodel.property.cm_addressee.title=\u53d7\u4fe1\u8005 +cm_contentmodel.property.cm_addressee.description=\u53d7\u4fe1\u8005 +cm_contentmodel.property.cm_addressees.title=\u53d7\u4fe1\u8005 +cm_contentmodel.property.cm_addressees.description=\u53d7\u4fe1\u8005 +cm_contentmodel.property.cm_subjectline.title=\u4ef6\u540d +cm_contentmodel.property.cm_subjectline.description=\u4ef6\u540d +cm_contentmodel.property.cm_sentdate.title=\u9001\u4fe1\u65e5 +cm_contentmodel.property.cm_sentdate.description=\u9001\u4fe1\u65e5 + +cm_contentmodel.aspect.cm_geographic.title=\u5730\u7406\u60c5\u5831\u4ed8\u304d +cm_contentmodel.aspect.cm_geographic.description=\u5730\u7406\u60c5\u5831\u4ed8\u304d +cm_contentmodel.property.cm_latitude.title=\u7def\u5ea6 +cm_contentmodel.property.cm_latitude.description=\u7def\u5ea6 +cm_contentmodel.property.cm_longitude.title=\u7d4c\u5ea6 +cm_contentmodel.property.cm_longitude.description=\u7d4c\u5ea6 + +cm_contentmodel.aspect.exif_exif.title=EXIF\u60c5\u5831\u4ed8\u304d +cm_contentmodel.aspect.exif_exif.description=\u6a19\u6e96EXIF\u30e1\u30bf\u30c7\u30fc\u30bf\u306e\u4ef6\u540d +cm_contentmodel.property.exif_dateTimeOriginal.title=\u65e5\u6642 +cm_contentmodel.property.exif_dateTimeOriginal.description=\u5143\u306e\u753b\u50cf\u304c\u751f\u6210\u3055\u308c\u305f\u65e5\u6642 +cm_contentmodel.property.exif_pixelXDimension.title=\u753b\u50cf\u5e45 +cm_contentmodel.property.exif_pixelXDimension.description=\u753b\u50cf\u5e45\uff08\u30d4\u30af\u30bb\u30eb\uff09 +cm_contentmodel.property.exif_pixelYDimension.title=\u753b\u50cf\u9ad8 +cm_contentmodel.property.exif_pixelYDimension.description=\u753b\u50cf\u9ad8\uff08\u30d4\u30af\u30bb\u30eb\uff09 +cm_contentmodel.property.exif_exposureTime.title=\u9732\u5149\u6642\u9593 +cm_contentmodel.property.exif_exposureTime.description=\u9732\u5149\u6642\u9593\uff08\u79d2\uff09 +cm_contentmodel.property.exif_fNumber.title=F\u30ca\u30f3\u30d0\u30fc +cm_contentmodel.property.exif_fNumber.description=F\u30ca\u30f3\u30d0\u30fc +cm_contentmodel.property.exif_flash.title=\u6709\u52b9\u306b\u306a\u3063\u305f\u30d5\u30e9\u30c3\u30b7\u30e5 +cm_contentmodel.property.exif_flash.description=\u5199\u771f\u3092\u64ae\u5f71\u3059\u308b\u3068\u304d\u3001\u30d5\u30e9\u30c3\u30b7\u30e5\u3092\u6709\u52b9\u306b\u3059\u308b\u304b\u3069\u3046\u304b +cm_contentmodel.property.exif_focalLength.title=\u7126\u70b9\u8ddd\u96e2 +cm_contentmodel.property.exif_focalLength.description=\u30ec\u30f3\u30ba\u306e\u7126\u70b9\u8ddd\u96e2\uff08\u30df\u30ea\u30e1\u30fc\u30c8\u30eb\uff09 +cm_contentmodel.property.exif_isoSpeedRatings.title=ISO\u901f\u5ea6 +cm_contentmodel.property.exif_isoSpeedRatings.description=ISO\u901f\u5ea6 +cm_contentmodel.property.exif_manufacturer.title=\u30ab\u30e1\u30e9\u30e1\u30fc\u30ab\u30fc +cm_contentmodel.property.exif_manufacturer.description=\u5199\u771f\u3092\u64ae\u5f71\u3057\u305f\u30ab\u30e1\u30e9\u306e\u30e1\u30fc\u30ab\u30fc +cm_contentmodel.property.exif_model.title=\u30ab\u30e1\u30e9\u30e2\u30c7\u30eb +cm_contentmodel.property.exif_model.description=\u5199\u771f\u3092\u64ae\u5f71\u3057\u305f\u30ab\u30e1\u30e9\u306e\u30e2\u30c7\u30eb +cm_contentmodel.property.exif_software.title=\u30ab\u30e1\u30e9\u30bd\u30d5\u30c8\u30a6\u30a7\u30a2 +cm_contentmodel.property.exif_software.description=\u5199\u771f\u3092\u64ae\u5f71\u3057\u305f\u30ab\u30e1\u30e9\u306e\u30bd\u30d5\u30c8\u30a6\u30a7\u30a2 +cm_contentmodel.property.exif_orientation.title=\u65b9\u5411 +cm_contentmodel.property.exif_orientation.description=\u5199\u771f\u306e\u65b9\u5411 +cm_contentmodel.property.exif_xResolution.title=\u6c34\u5e73\u89e3\u50cf\u5ea6 +cm_contentmodel.property.exif_xResolution.description=\u6c34\u5e73\u89e3\u50cf\u5ea6\uff08\u30d4\u30af\u30bb\u30eb/\u5358\u4f4d\uff09 +cm_contentmodel.property.exif_yResolution.title=\u5782\u76f4\u89e3\u50cf\u5ea6 +cm_contentmodel.property.exif_yResolution.description=\u5782\u76f4\u89e3\u50cf\u5ea6\uff08\u30d4\u30af\u30bb\u30eb/\u5358\u4f4d\uff09 +cm_contentmodel.property.exif_resolutionUnit.title=\u89e3\u50cf\u5ea6\u5358\u4f4d +cm_contentmodel.property.exif_resolutionUnit.description=\u6c34\u5e73\u304a\u3088\u3073\u5782\u76f4\u89e3\u50cf\u5ea6\u306b\u4f7f\u7528\u3055\u308c\u308b\u5358\u4f4d + diff --git a/config/alfresco/messages/content-service_de.properties b/config/alfresco/messages/content-service_de.properties index 2e9a89fd19..abec384b00 100755 --- a/config/alfresco/messages/content-service_de.properties +++ b/config/alfresco/messages/content-service_de.properties @@ -23,4 +23,4 @@ metadata.extraction.err.type_conversion=Metadata extraction failed because an ex transform.err.format_or_password=Failed to convert content, possibly due to an incorrectly formatted or password protected file. -content.routing.err.invalid_default_store=The 'defaultStoreName', ''{0}'' does not refer to a store in 'storesByName' ({1}). \ No newline at end of file +content.routing.err.invalid_default_store=The 'defaultStoreName', ''{0}'' does not refer to a store in 'storesByName' ({1}). diff --git a/config/alfresco/messages/content-service_es.properties b/config/alfresco/messages/content-service_es.properties index 2e9a89fd19..abec384b00 100755 --- a/config/alfresco/messages/content-service_es.properties +++ b/config/alfresco/messages/content-service_es.properties @@ -23,4 +23,4 @@ metadata.extraction.err.type_conversion=Metadata extraction failed because an ex transform.err.format_or_password=Failed to convert content, possibly due to an incorrectly formatted or password protected file. -content.routing.err.invalid_default_store=The 'defaultStoreName', ''{0}'' does not refer to a store in 'storesByName' ({1}). \ No newline at end of file +content.routing.err.invalid_default_store=The 'defaultStoreName', ''{0}'' does not refer to a store in 'storesByName' ({1}). diff --git a/config/alfresco/messages/content-service_fr.properties b/config/alfresco/messages/content-service_fr.properties index 2e9a89fd19..abec384b00 100755 --- a/config/alfresco/messages/content-service_fr.properties +++ b/config/alfresco/messages/content-service_fr.properties @@ -23,4 +23,4 @@ metadata.extraction.err.type_conversion=Metadata extraction failed because an ex transform.err.format_or_password=Failed to convert content, possibly due to an incorrectly formatted or password protected file. -content.routing.err.invalid_default_store=The 'defaultStoreName', ''{0}'' does not refer to a store in 'storesByName' ({1}). \ No newline at end of file +content.routing.err.invalid_default_store=The 'defaultStoreName', ''{0}'' does not refer to a store in 'storesByName' ({1}). diff --git a/config/alfresco/messages/content-service_it.properties b/config/alfresco/messages/content-service_it.properties index 2e9a89fd19..abec384b00 100755 --- a/config/alfresco/messages/content-service_it.properties +++ b/config/alfresco/messages/content-service_it.properties @@ -23,4 +23,4 @@ metadata.extraction.err.type_conversion=Metadata extraction failed because an ex transform.err.format_or_password=Failed to convert content, possibly due to an incorrectly formatted or password protected file. -content.routing.err.invalid_default_store=The 'defaultStoreName', ''{0}'' does not refer to a store in 'storesByName' ({1}). \ No newline at end of file +content.routing.err.invalid_default_store=The 'defaultStoreName', ''{0}'' does not refer to a store in 'storesByName' ({1}). diff --git a/config/alfresco/messages/content-service_ja.properties b/config/alfresco/messages/content-service_ja.properties new file mode 100755 index 0000000000..abec384b00 --- /dev/null +++ b/config/alfresco/messages/content-service_ja.properties @@ -0,0 +1,26 @@ +# Content-related messages + +content.content_missing=The node''s content is missing: \n node: {0} \n reader: {1} \n Please contact your system administrator. +content.runtime_exec.property_moved=The property ''errorCodes'' has moved down onto the RuntimeExec class + +index.recovery.out_of_date=The indexes are not synchronized with the database. +index.tracking.starting=Index recovery started. +index.tracking.complete=Index recovery completed. +index.tracking.progress=\tProcessing transactions around {0}. +index.recovery.starting=Index recovery started: {0} transactions. +index.recovery.complete=Index recovery completed. +index.recovery.progress=\t{0} % complete. +index.recovery.terminated=Index recovery terminated. + +node.archive.msg.busy=A bulk purge or restore operation is currently in progress + +content.http_reader.err.no_connection=Unable to connect to remote Alfresco server via HTTP: {0} +content.http_reader.err.no_authentication=The HTTP reader was unable to authenticate on the remote server: {0} \n +content.http_reader.err.check_cluster=Please ensure that 'replicateUpdates' and 'replicateUpdatesViaCopy' is enabled for the cache 'org.alfresco.cache.ticketsCache'. Check that the general cluster configuration is correct and working. +content.http_reader.err.unrecognized=An unrecognized error occurred when attempting to download content from remote server:\n Server: {0} \n Content: {1} \n HTTP Response: {2} + +metadata.extraction.err.type_conversion=Metadata extraction failed because an extracted value failed to convert to the required type: \n Extractor: {0} \n Target Property QName: {1} \n Required Type: {2} \n Extracted Value: {3} + +transform.err.format_or_password=Failed to convert content, possibly due to an incorrectly formatted or password protected file. + +content.routing.err.invalid_default_store=The 'defaultStoreName', ''{0}'' does not refer to a store in 'storesByName' ({1}). diff --git a/config/alfresco/messages/copy-service_ja.properties b/config/alfresco/messages/copy-service_ja.properties new file mode 100755 index 0000000000..79d8c873fa --- /dev/null +++ b/config/alfresco/messages/copy-service_ja.properties @@ -0,0 +1,3 @@ +# copy service externalised display strings + +copy_service.copy_of_label={0} \u306e\u30b3\u30d4\u30fc diff --git a/config/alfresco/messages/data-list-model_fr.properties b/config/alfresco/messages/data-list-model_fr.properties index 812a453f8f..ce8a674aa0 100755 --- a/config/alfresco/messages/data-list-model_fr.properties +++ b/config/alfresco/messages/data-list-model_fr.properties @@ -20,7 +20,7 @@ dl_datalistmodel.property.dl_todoNotes.title=Notes dl_datalistmodel.association.dl_assignee.title=Personne assign\u00e9e # Gantt Aspect -dl_datalistmodel.property.dl_ganttStartDate.title=Date de D\u00e9but +dl_datalistmodel.property.dl_ganttStartDate.title=Date de d\u00e9but dl_datalistmodel.property.dl_ganttEndDate.title=Date de fin dl_datalistmodel.property.dl_ganttPercentComplete.title=% achev\u00e9 diff --git a/config/alfresco/messages/data-list-model_ja.properties b/config/alfresco/messages/data-list-model_ja.properties new file mode 100755 index 0000000000..57c21ab98e --- /dev/null +++ b/config/alfresco/messages/data-list-model_ja.properties @@ -0,0 +1,103 @@ +# Display labels for Share Data Lists Model +dl_datalistmodel.description=Alfresco Share Data List Model + +dl_datalistmodel.type.dl_dataList.title=\u30c7\u30fc\u30bf\u30ea\u30b9\u30c8\u30fb\u30d5\u30a9\u30eb\u30c0\u30bf\u30a4\u30d7 +dl_datalistmodel.type.dl_dataList.description= dl:dataListItemType\u30d7\u30ed\u30d1\u30c6\u30a3\u3067\u6307\u5b9a\u3055\u308c\u308b\u30bf\u30a4\u30d7\u306e\u30c7\u30fc\u30bf\u30ea\u30b9\u30c8\u30a2\u30a4\u30c6\u30e0\u3092\u4fdd\u6301\u3057\u307e\u3059\u3002 +dl_datalistmodel.property.dl_dataListItemType.title=\u30c7\u30fc\u30bf\u30ea\u30b9\u30c8\u30fb\u30a2\u30a4\u30c6\u30e0\u30bf\u30a4\u30d7 +dl_datalistmodel.property.dl_dataListItemType.description=\u30c7\u30fc\u30bf\u30ea\u30b9\u30c8\u5185\u306b\u65b0\u898f\u30a2\u30a4\u30c6\u30e0\u304c\u4f5c\u3089\u308c\u305f\u6642\u306b\u3001\u3069\u306edl:dataListItem\u306e\u30b5\u30d6\u30bf\u30a4\u30d7\u3092\u4f7f\u7528\u3059\u308b\u304b\u3092\u6c7a\u5b9a\u3057\u307e\u3059\u3002 + +dl_datalistmodel.type.dl_dataListItem.title=\u30c7\u30fc\u30bf\u30ea\u30b9\u30c8\u89aa\u30bf\u30a4\u30d7 +dl_datalistmodel.type.dl_dataListItem.description=\u30b5\u30f3\u30d7\u30eb\u306e\u30c7\u30fc\u30bf\u30ea\u30b9\u30c8\u30fb\u30a2\u30a4\u30c6\u30e0\u30bf\u30a4\u30d7\u3092\u6d3e\u751f\u3059\u308b\u89aa\u30bf\u30a4\u30d7\u3002 + +## Simple "To do" List +dl_datalistmodel.type.dl_todoList.title=\u4e88\u5b9a\u30ea\u30b9\u30c8 +dl_datalistmodel.type.dl_todoList.description=\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u62c5\u5f53\u8005\u3092\u5272\u308a\u5f53\u3066\u308b\u5358\u7d14\u306a\u30ea\u30b9\u30c8 +dl_datalistmodel.property.dl_todoTitle.title=\u30bf\u30a4\u30c8\u30eb +dl_datalistmodel.property.dl_todoDueDate.title=\u7de0\u5207\u65e5 +dl_datalistmodel.property.dl_todoPriority.title=\u512a\u5148 +dl_datalistmodel.property.dl_todoStatus.title=\u30b9\u30c6\u30fc\u30bf\u30b9 +dl_datalistmodel.property.dl_todoNotes.title=\u30e1\u30e2 +dl_datalistmodel.association.dl_assignee.title=\u62c5\u5f53\u8005 + +# Gantt Aspect +dl_datalistmodel.property.dl_ganttStartDate.title=\u958b\u59cb\u65e5 +dl_datalistmodel.property.dl_ganttEndDate.title=\u7d42\u4e86\u65e5 +dl_datalistmodel.property.dl_ganttPercentComplete.title=% \u5b8c\u4e86 + +# Task List (Advanced) +dl_datalistmodel.type.dl_task.title=\u30bf\u30b9\u30af\u30ea\u30b9\u30c8\uff08\u8a73\u7d30\u7248\uff09 +dl_datalistmodel.type.dl_task.description=\u8a73\u7d30\u7248\u30bf\u30b9\u30af\u30ea\u30b9\u30c8\u306b\u306f\u30bf\u30a4\u30c8\u30eb\u3001\u8aac\u660e\u3001\u958b\u59cb\u65e5\u3068\u7d42\u4e86\u65e5\u3001\u512a\u5148\u5ea6\u3001\u30b9\u30c6\u30fc\u30bf\u30b9\u3001\u30b3\u30e1\u30f3\u30c8\u3001\u62c5\u5f53\u8005\u3001\u6dfb\u4ed8\u306a\u3069\u304c\u542b\u307e\u308c\u307e\u3059\u3002 +dl_datalistmodel.property.dl_taskPriority.title=\u512a\u5148 +dl_datalistmodel.property.dl_taskStatus.title=\u30b9\u30c6\u30fc\u30bf\u30b9 +dl_datalistmodel.property.dl_taskComments.title=\u30b3\u30e1\u30f3\u30c8 +dl_datalistmodel.property.dl_taskAssignee.title=\u62c5\u5f53\u8005 + +# Task List (Simple) +dl_datalistmodel.type.dl_simpletask.title=\u30bf\u30b9\u30af\u30ea\u30b9\u30c8\uff08\u7c21\u6613\u7248\uff09 +dl_datalistmodel.type.dl_simpletask.description=\u7c21\u6613\u7248\u30bf\u30b9\u30af\u30ea\u30b9\u30c8\u306b\u306f\u30bf\u30a4\u30c8\u30eb\u3001\u8aac\u660e\u3001\u7de0\u5207\u65e5\u3001\u512a\u5148\u3001\u30b9\u30c6\u30fc\u30bf\u30b9\u3001\u30b3\u30e1\u30f3\u30c8\u306a\u3069\u304c\u542b\u307e\u308c\u307e\u3059\u3002 +dl_datalistmodel.property.dl_simpletaskDueDate.title=\u7de0\u5207\u65e5 +dl_datalistmodel.property.dl_simpletaskPriority.title=\u512a\u5148 +dl_datalistmodel.property.dl_simpletaskStatus.title=\u30b9\u30c6\u30fc\u30bf\u30b9 +dl_datalistmodel.property.dl_simpletaskComments.title=\u30b3\u30e1\u30f3\u30c8 + +# Contact +dl_datalistmodel.type.dl_contact.title=\u30b3\u30f3\u30bf\u30af\u30c8\u30ea\u30b9\u30c8 +dl_datalistmodel.type.dl_contact.description=\u30b3\u30f3\u30bf\u30af\u30c8\u30ea\u30b9\u30c8\u306b\u306f\u3001\u59d3\u3001\u540d\u3001\u30d5\u30eb\u30cd\u30fc\u30e0\u3001E\u30e1\u30fc\u30eb\u3001\u5f79\u8077\u540d\u3001\u96fb\u8a71\uff08\u30aa\u30d5\u30a3\u30b9\uff09\u3001\u96fb\u8a71\uff08\u643a\u5e2f\uff09\u304c\u542b\u307e\u308c\u307e\u3059\u3002 +dl_datalistmodel.property.dl_contactFirstName.title=\u540d +dl_datalistmodel.property.dl_contactLastName.title=\u59d3 +dl_datalistmodel.property.dl_contactEmail.title=E\u30e1\u30fc\u30eb +dl_datalistmodel.property.dl_contactCompany.title=\u4f1a\u793e +dl_datalistmodel.property.dl_contactJobTitle.title=\u5f79\u8077\u540d +dl_datalistmodel.property.dl_contactPhoneOffice.title=\u96fb\u8a71\uff08\u30aa\u30d5\u30a3\u30b9\uff09 +dl_datalistmodel.property.dl_contactPhoneMobile.title=\u96fb\u8a71\uff08\u643a\u5e2f\uff09 +dl_datalistmodel.property.dl_contactNotes.title=\u88dc\u8db3 + +# Issues +dl_datalistmodel.type.dl_issue.title=\u30a4\u30b7\u30e5\u30fc\u30ea\u30b9\u30c8 +dl_datalistmodel.type.dl_issue.description=\u30a4\u30b7\u30e5\u30fc\u30ea\u30b9\u30c8\u306b\u306fID\u3001\u30b9\u30c6\u30fc\u30bf\u30b9\u3001\u512a\u5148\u3001\u8aac\u660e\u3001\u7de0\u5207\u65e5\u3001\u30b3\u30e1\u30f3\u30c8\u3001\u62c5\u5f53\u8005\u3001\u95a2\u9023\u554f\u984c\u304c\u542b\u307e\u308c\u307e\u3059\u3002 +dl_datalistmodel.property.dl_issueID.title=\u30a4\u30b7\u30e5\u30fcID +dl_datalistmodel.property.dl_issueStatus.title=\u30b9\u30c6\u30fc\u30bf\u30b9 +dl_datalistmodel.property.dl_issuePriority.title=\u512a\u5148 +dl_datalistmodel.property.dl_issueDescription.title=\u8aac\u660e +dl_datalistmodel.property.dl_issueDueDate.title=\u7de0\u5207\u65e5 +dl_datalistmodel.property.dl_issueComments.title=\u30b3\u30e1\u30f3\u30c8 +dl_datalistmodel.property.dl_issueAssignedTo.title=\u62c5\u5f53\u8005 +dl_datalistmodel.property.dl_issueRelatedIssues.title=\u95a2\u9023\u8ab2\u984c + +# Event +dl_datalistmodel.type.dl_event.title=\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30c8 +dl_datalistmodel.type.dl_event.description=\u30a4\u30d9\u30f3\u30c8\u30ea\u30b9\u30c8\u306b\u306f\u3001\u30bf\u30a4\u30c8\u30eb\u3001\u8aac\u660e\u3001\u5834\u6240\u3001\u958b\u59cb\u65e5\u6642/\u7d42\u4e86\u65e5\u6642\u304c\u542b\u307e\u308c\u307e\u3059\u3002 +dl_datalistmodel.property.dl_eventLocation.title=\u5834\u6240 +dl_datalistmodel.property.dl_eventStartDate.title=\u958b\u59cb\u65e5 +dl_datalistmodel.property.dl_eventEndDate.title=\u7d42\u4e86\u65e5 +dl_datalistmodel.property.dl_eventRegistrations.title=\u767b\u9332 +dl_datalistmodel.property.dl_eventNote.title=\u88dc\u8db3 + +# Location +dl_datalistmodel.type.dl_location.title=\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3\u30ea\u30b9\u30c8 +dl_datalistmodel.type.dl_location.description=\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3/\u4f4f\u6240\u30ea\u30b9\u30c8 +dl_datalistmodel.property.dl_locationAddress1.title=\u4f4f\u6240\uff081\u884c\u76ee\uff09 +dl_datalistmodel.property.dl_locationAddress2.title=\u4f4f\u6240\uff082\u884c\u76ee\uff09 +dl_datalistmodel.property.dl_locationAddress3.title=\u4f4f\u6240\uff083\u884c\u76ee\uff09 +dl_datalistmodel.property.dl_locationZip.title=\u90f5\u4fbf\u756a\u53f7 +dl_datalistmodel.property.dl_locationState.title=\u90fd\u9053\u5e9c\u770c +dl_datalistmodel.property.dl_locationCountry.title=\u56fd +dl_datalistmodel.property.dl_locationNote.title=\u88dc\u8db3 + +# Meeting Agenda +dl_datalistmodel.type.dl_meetingAgenda.title=\u4f1a\u8b70\u30a2\u30b8\u30a7\u30f3\u30c0 +dl_datalistmodel.type.dl_meetingAgenda.description=\u4f1a\u8b70\u30a2\u30b8\u30a7\u30f3\u30c0\u306e\u30a2\u30a4\u30c6\u30e0\u306b\u306f\u8aac\u660e\u3001\u6240\u6709\u8005\u3001\u5145\u5f53\u6642\u9593\u304c\u542b\u307e\u308c\u307e\u3059\u3002 +dl_datalistmodel.property.dl_meetingAgendaRef.title=\u53c2\u7167 +dl_datalistmodel.property.dl_meetingAgendaTime.title=\u6642\u9593\uff08\u5206\uff09 +dl_datalistmodel.property.dl_meetingAgendaOwner.title=\u6240\u6709\u8005 + +# Event Agenda +dl_datalistmodel.type.dl_eventAgenda.title=\u30a4\u30d9\u30f3\u30c8\u30a2\u30b8\u30a7\u30f3\u30c0 +dl_datalistmodel.type.dl_eventAgenda.description=\u30a4\u30d9\u30f3\u30c8\u30a2\u30b8\u30a7\u30f3\u30c0\u7ba1\u7406\u30a2\u30a4\u30c6\u30e0\u306b\u306f\u30bb\u30c3\u30b7\u30e7\u30f3\u540d\u3001\u767a\u8868\u8005\u3001\u958b\u59cb/\u7d42\u4e86\u6642\u9593\u304c\u542b\u307e\u308c\u307e\u3059\u3002 +dl_datalistmodel.property.dl_eventAgendaRef.title=\u53c2\u7167 +dl_datalistmodel.property.dl_eventAgendaStartTime.title=\u958b\u59cb\u6642\u9593 +dl_datalistmodel.property.dl_eventAgendaEndTime.title=\u7d42\u4e86\u6642\u9593 +dl_datalistmodel.property.dl_eventAgendaSessionName.title=\u30bb\u30c3\u30b7\u30e7\u30f3\u540d +dl_datalistmodel.property.dl_eventAgendaPresenter.title=\u767a\u8868\u8005 +dl_datalistmodel.property.dl_eventAgendaAudience.title=\u8074\u8846 +dl_datalistmodel.property.dl_eventAgendaNotes.title=\u88dc\u8db3 diff --git a/config/alfresco/messages/discussion-messages_fr.properties b/config/alfresco/messages/discussion-messages_fr.properties index 973273fdc9..170f29d414 100755 --- a/config/alfresco/messages/discussion-messages_fr.properties +++ b/config/alfresco/messages/discussion-messages_fr.properties @@ -1,3 +1,3 @@ # Discussion-related messages -discussion.discussion_for=Discussion {0} +discussion.discussion_for=discussion {0} diff --git a/config/alfresco/messages/discussion-messages_it.properties b/config/alfresco/messages/discussion-messages_it.properties index 5ef92284f2..33b71e431e 100755 --- a/config/alfresco/messages/discussion-messages_it.properties +++ b/config/alfresco/messages/discussion-messages_it.properties @@ -1,3 +1,3 @@ # Discussion-related messages -discussion.discussion_for=Discussione {0} +discussion.discussion_for={0}discussione diff --git a/config/alfresco/messages/discussion-messages_ja.properties b/config/alfresco/messages/discussion-messages_ja.properties new file mode 100755 index 0000000000..f67349ba7d --- /dev/null +++ b/config/alfresco/messages/discussion-messages_ja.properties @@ -0,0 +1,3 @@ +# Discussion-related messages + +discussion.discussion_for={0} \u30c7\u30a3\u30b9\u30ab\u30c3\u30b7\u30e7\u30f3 diff --git a/config/alfresco/messages/email-server-model_ja.properties b/config/alfresco/messages/email-server-model_ja.properties new file mode 100755 index 0000000000..589869a19a --- /dev/null +++ b/config/alfresco/messages/email-server-model_ja.properties @@ -0,0 +1,4 @@ +emailserver_emailserverModel.aspect.emailserver_aliasable.title=E\u30e1\u30fc\u30eb\u30a8\u30a4\u30ea\u30a2\u30b9 +emailserver_emailserverModel.aspect.emailserver_aliasable.description=E\u30e1\u30fc\u30eb\u30a8\u30a4\u30ea\u30a2\u30b9 +emailserver_emailserverModel.property.emailserver_alias.title=\u5225\u540d +emailserver_emailserverModel.property.emailserver_alias.description=\u5225\u540d diff --git a/config/alfresco/messages/email-service_it.properties b/config/alfresco/messages/email-service_it.properties index 1384253f46..c5cd1b375f 100755 --- a/config/alfresco/messages/email-service_it.properties +++ b/config/alfresco/messages/email-service_it.properties @@ -1,9 +1,9 @@ email.server.msg.received_by_smtp=Ricevuto tramite SMTP da ''{0}''. -email.server.msg.default_subject=E-mail - {0} +email.server.msg.default_subject=E-mail-{0} -email.server.err.sender_blocked=A ''{0}'' \u00e8 stato negato l'accesso. +email.server.err.sender_blocked=''{0}'' a \u00e8 stato negato l'accesso. email.server.err.inbound_mail_disabled=Il server Alfresco non \u00e8 configurato in modo da accettare e-mail in arrivo. -email.server.err.access_denied=A ''{0}'' \u00e8 stato negato l'accesso per ''{1}''. +email.server.err.access_denied=''{0}'' a \u00e8 stato negato l'accesso per ''{1}''. email.server.err.invalid_subject=La riga dell'oggetto deve contenere un nome di file valido. email.server.err.unknown_source_address=L'indirizzo e-mail del mittente non \u00e8 stato riconosciuto: {0}. email.server.err.user_not_email_contributor=L'utente ''{0}'' non fa parte del gruppo di contributori ai messaggi e-mail. diff --git a/config/alfresco/messages/email-service_ja.properties b/config/alfresco/messages/email-service_ja.properties new file mode 100755 index 0000000000..d1afd5a2c2 --- /dev/null +++ b/config/alfresco/messages/email-service_ja.properties @@ -0,0 +1,24 @@ +email.server.msg.received_by_smtp=''{0}''\u304b\u3089SMTP\u3067\u53d7\u4fe1\u3057\u307e\u3057\u305f\u3002 +email.server.msg.default_subject=E\u30e1\u30fc\u30eb-{0} + +email.server.err.sender_blocked=''{0}''\u306e\u30a2\u30af\u30bb\u30b9\u304c\u62d2\u5426\u3055\u308c\u307e\u3057\u305f\u3002 +email.server.err.inbound_mail_disabled=Alfresco\u30b5\u30fc\u30d0\u304c\u7740\u4fe1E\u30e1\u30fc\u30eb\u3092\u53d7\u3051\u5165\u308c\u308b\u3088\u3046\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +email.server.err.access_denied=''{0}''\u306e''{1}''\u3078\u306e\u30a2\u30af\u30bb\u30b9\u304c\u62d2\u5426\u3055\u308c\u307e\u3057\u305f\u3002 +email.server.err.invalid_subject=\u4ef6\u540d\u884c\u306f\u6709\u52b9\u306a\u30d5\u30a1\u30a4\u30eb\u540d\u306b\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 +email.server.err.unknown_source_address='\u9001\u4fe1\u5143'E\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u304c\u8a8d\u8b58\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f: {0}\u3002 +email.server.err.user_not_email_contributor=\u30e6\u30fc\u30b6''{0}''\u306fE\u30e1\u30fc\u30eb\u30b3\u30f3\u30c8\u30ea\u30d3\u30e5\u30fc\u30bf\u30b0\u30eb\u30fc\u30d7\u306b\u542b\u307e\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +email.server.err.no_email_contributor_group=E\u30e1\u30fc\u30eb\u30b3\u30f3\u30c8\u30ea\u30d3\u30e5\u30fc\u30bf\u30b0\u30eb\u30fc\u30d7\u304c\u5b58\u5728\u3057\u307e\u305b\u3093\u3002 +email.server.err.invalid_node_address=E\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9''{0}''\u306f\u6709\u52b9\u306a\u30a2\u30af\u30bb\u30b9\u53ef\u80fd\u30ce\u30fc\u30c9\u3092\u53c2\u7167\u3057\u3066\u3044\u307e\u305b\u3093\u3002 +email.server.err.handler_not_found=\u30ce\u30fc\u30c9\u30bf\u30a4\u30d7''{0}''\u306eE\u30e1\u30fc\u30eb\u30e1\u30c3\u30bb\u30fc\u30b8\u30cf\u30f3\u30c9\u30e9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 +email.server.err.mail_read_error=E\u30e1\u30fc\u30eb\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u8aad\u53d6\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0} +email.server.err.failed_to_create_mime_message=\u5165\u529b\u30b9\u30c8\u30ea\u30fc\u30e0\u304b\u3089MIME\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u751f\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0} +email.server.err.extracting_from_address='\u9001\u4fe1\u5143'\u30a2\u30c9\u30ec\u30b9\u306e\u62bd\u51fa\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0} +email.server.err.no_from_address=\u30e1\u30c3\u30bb\u30fc\u30b8\u306b'\u9001\u4fe1\u5143'\u30a2\u30c9\u30ec\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +email.server.err.extracting_to_address=\u5b9b\u5148'\u30a2\u30c9\u30ec\u30b9\u306e\u62bd\u51fa\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0} +email.server.err.no_to_address=\u30e1\u30c3\u30bb\u30fc\u30b8\u306b'\u5b9b\u5148'\u30a2\u30c9\u30ec\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +email.server.err.extracting_subject=\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u4ef6\u540d\u306e\u62bd\u51fa\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0} +email.server.err.extracting_sent_date='\u9001\u4fe1\u65e5'\u306e\u65e5\u4ed8\u306e\u62bd\u51fa\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0} +email.server.err.parse_message=E\u30e1\u30fc\u30eb\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u30d1\u30fc\u30b9\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0} +email.server.err.usupported_encoding=\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0''{0}''\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u307e\u305b\u3093 +email.server.err.failed_to_read_content_stream=\u30e1\u30c3\u30bb\u30fc\u30b8\u90e8\u5206\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u8aad\u8fbc\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0} +email.server.err.incorrect_message_part=\u9593\u9055\u3063\u305f\u30e1\u30c3\u30bb\u30fc\u30b8\u90e8\u5206: {0} diff --git a/config/alfresco/messages/form-service_fr.properties b/config/alfresco/messages/form-service_fr.properties index b15b474d05..876e84e690 100755 --- a/config/alfresco/messages/form-service_fr.properties +++ b/config/alfresco/messages/form-service_fr.properties @@ -8,14 +8,14 @@ form_service.size.label=Taille form_service.size.description=Taille du contenu, en octets form_service.message.label=Message -form_service.message.description=Message saisi par l'utilisateur lors du d\u00e9marrage du flux de travail +form_service.message.description=Message saisi par l'utilisateur lors du d\u00e9marrage du workflow form_service.message.value.none=(Aucun message) form_service.transitions.label=Transitions form_service.transitions.description=Transitions disponibles pour la t\u00e2che form_service.package.items.label=El\u00e9ment(s) -form_service.package.items.description=\u00c9l\u00e9ments faisant partie du flux de travail +form_service.package.items.description=\u00c9l\u00e9ments faisant partie du workflow form_service.task.owner.label=Propri\u00e9taire form_service.task.owner.description=Utilisateur propri\u00e9taire de la t\u00e2che diff --git a/config/alfresco/messages/form-service_ja.properties b/config/alfresco/messages/form-service_ja.properties new file mode 100755 index 0000000000..d5f87e4066 --- /dev/null +++ b/config/alfresco/messages/form-service_ja.properties @@ -0,0 +1,21 @@ +# form service externalised display strings + +form_service.mimetype.label=MIME\u30bf\u30a4\u30d7 +form_service.mimetype.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u306eMIME\u30bf\u30a4\u30d7 +form_service.encoding.label=\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0 +form_service.encoding.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0 +form_service.size.label=\u30b5\u30a4\u30ba +form_service.size.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30b5\u30a4\u30ba(\u30d0\u30a4\u30c8) + +form_service.message.label=\u30e1\u30c3\u30bb\u30fc\u30b8 +form_service.message.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u958b\u59cb\u6642\u306b\u30e6\u30fc\u30b6\u304c\u5165\u529b\u3057\u305f\u30e1\u30c3\u30bb\u30fc\u30b8 +form_service.message.value.none=(\u30e1\u30c3\u30bb\u30fc\u30b8\u306a\u3057) + +form_service.transitions.label=\u30c8\u30e9\u30f3\u30b8\u30b7\u30e7\u30f3 +form_service.transitions.description=\u30bf\u30b9\u30af\u3067\u4f7f\u7528\u53ef\u80fd\u306a\u30c8\u30e9\u30f3\u30b8\u30b7\u30e7\u30f3 + +form_service.package.items.label=\u30a2\u30a4\u30c6\u30e0 +form_service.package.items.description=\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u306e\u4e00\u90e8\u306e\u30a2\u30a4\u30c6\u30e0 + +form_service.task.owner.label=\u6240\u6709\u8005 +form_service.task.owner.description=\u30bf\u30b9\u30af\u3092\u6240\u6709\u3059\u308b\u30e6\u30fc\u30b6 diff --git a/config/alfresco/messages/forum-model_fr.properties b/config/alfresco/messages/forum-model_fr.properties index 4a5fe4962a..1bc33204c0 100755 --- a/config/alfresco/messages/forum-model_fr.properties +++ b/config/alfresco/messages/forum-model_fr.properties @@ -12,7 +12,7 @@ fm_forummodel.type.fm_topic.title=Discussion fm_forummodel.type.fm_topic.description=Espace contenant les articles fm_forummodel.type.fm_post.title=Article du forum -fm_forummodel.type.fm_post.description=Article du forum post\u00e9 sur un sujet +fm_forummodel.type.fm_post.description=Article du forum publi\u00e9 sur un sujet fm_forummodel.aspect.fm_discussable.title=A d\u00e9battre fm_forummodel.aspect.fm_discussable.description=Autorise une discussion sur un \u00e9l\u00e9ment diff --git a/config/alfresco/messages/forum-model_it.properties b/config/alfresco/messages/forum-model_it.properties index f9b7b84084..ad1c87350c 100755 --- a/config/alfresco/messages/forum-model_it.properties +++ b/config/alfresco/messages/forum-model_it.properties @@ -18,4 +18,4 @@ fm_forummodel.aspect.fm_discussable.title=Discussable fm_forummodel.aspect.fm_discussable.description=Consente di creare una discussione su un oggetto fm_forummodel.association.fm_discussion.title=Discussione -fm_forummodel.association.fm_discussion.description=Il forum contenente la discussione sull'oggetto a cui viene applicato l'aspetto \ No newline at end of file +fm_forummodel.association.fm_discussion.description=Il forum contenente la discussione sull'oggetto a cui viene applicato l'aspetto diff --git a/config/alfresco/messages/forum-model_ja.properties b/config/alfresco/messages/forum-model_ja.properties new file mode 100755 index 0000000000..8a5965fe23 --- /dev/null +++ b/config/alfresco/messages/forum-model_ja.properties @@ -0,0 +1,21 @@ +# Display labels for System Model + +fm_forummodel.description=\u30d5\u30a9\u30fc\u30e9\u30e0\u30e2\u30c7\u30eb + +fm_forummodel.type.fm_forums.title=\u30d5\u30a9\u30fc\u30e9\u30e0\u30b9\u30da\u30fc\u30b9 +fm_forummodel.type.fm_forums.description=\u30d5\u30a9\u30fc\u30e9\u30e0\u3092\u542b\u3080\u30b9\u30da\u30fc\u30b9 + +fm_forummodel.type.fm_forum.title=\u30d5\u30a9\u30fc\u30e9\u30e0 +fm_forummodel.type.fm_forum.description=\u30c8\u30d4\u30c3\u30af\u3092\u542b\u3080\u30b9\u30da\u30fc\u30b9 + +fm_forummodel.type.fm_topic.title=\u30c8\u30d4\u30c3\u30af +fm_forummodel.type.fm_topic.description=\u30d5\u30a9\u30fc\u30e9\u30e0\u306e\u8a18\u4e8b\uff08\u6295\u7a3f\uff09\u3092\u542b\u3080\u30b9\u30da\u30fc\u30b9 + +fm_forummodel.type.fm_post.title=\u30d5\u30a9\u30fc\u30e9\u30e0\u306e\u8a18\u4e8b +fm_forummodel.type.fm_post.description=\u6295\u7a3f\u3055\u308c\u305f\u30d5\u30a9\u30fc\u30e9\u30e0\u306e\u8a18\u4e8b + +fm_forummodel.aspect.fm_discussable.title=\u30c7\u30a3\u30b9\u30ab\u30c3\u30b7\u30e7\u30f3\u53ef\u80fd +fm_forummodel.aspect.fm_discussable.description=\u30c7\u30a3\u30b9\u30ab\u30c3\u30b7\u30e7\u30f3\u3059\u308b\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u3092\u8a31\u53ef\u3059\u308b + +fm_forummodel.association.fm_discussion.title=\u30c7\u30a3\u30b9\u30ab\u30c3\u30b7\u30e7\u30f3 +fm_forummodel.association.fm_discussion.description=\u30a2\u30b9\u30da\u30af\u30c8\u304c\u9069\u7528\u3055\u308c\u308b\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306b\u95a2\u3059\u308b\u30c7\u30a3\u30b9\u30ab\u30c3\u30b7\u30e7\u30f3\u304c\u958b\u50ac\u3055\u308c\u3066\u3044\u308b\u30d5\u30a9\u30fc\u30e9\u30e0 diff --git a/config/alfresco/messages/imap-service_ja.properties b/config/alfresco/messages/imap-service_ja.properties new file mode 100755 index 0000000000..f61a7a8378 --- /dev/null +++ b/config/alfresco/messages/imap-service_ja.properties @@ -0,0 +1,14 @@ +# +# Imap I18N messages +# + +# Information messages. prefix 'imap.server.info' +imap.server.info.message_body_not_found ="\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u672c\u6587\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002" + +# Error messages. prefix 'imap.server.error' +imap.server.error.properties_dont_exist ="\u9069\u5207\u306a\u30d7\u30ed\u30d1\u30c6\u30a3\u304c\u5b58\u5728\u3057\u3066\u3044\u307e\u305b\u3093\u3002" +imap.server.error.permission_denied ="\u30d5\u30a9\u30eb\u30c0\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093 - \u6a29\u9650\u304c\u62d2\u5426\u3055\u308c\u307e\u3057\u305f\u3002" +imap.server.error.folder_already_exist ="\u30d5\u30a9\u30eb\u30c0\u304c\u3059\u3067\u306b\u5b58\u5728\u3057\u3066\u3044\u307e\u3059\u3002" +imap.server.error.mailbox_name_is_mandatory ="\u30e1\u30fc\u30eb\u30dc\u30c3\u30af\u30b9\u540d\u306f\u5fc5\u9808\u30d1\u30e9\u30e1\u30fc\u30bf\u3067\u3059\u3002" +imap.server.error.cannot_get_a_folder ="\u540d\u524d ''{0}'' \u3067\u30d5\u30a9\u30eb\u30c0\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3002" + diff --git a/config/alfresco/messages/invitation-service.properties b/config/alfresco/messages/invitation-service.properties index f9e6aaf182..bd91784f40 100644 --- a/config/alfresco/messages/invitation-service.properties +++ b/config/alfresco/messages/invitation-service.properties @@ -6,14 +6,14 @@ invitation.error.noworkflow="Invitation workflow not found, workflow name : {0}" invitation.error.not_found="Invitation not found, invitationId: {0}" invitation.error.invalid_inviteId_format="Invitation Id not valid format, valid formats are $ : {0}" invitation.invite.already_member="The user, {0} is already a member of {1} and cannot be invited again" -invitation.cancel.not_site_manager="Current user, {0}, cannot cancel invitation: {1} because they are not a Site Manager for site: {2} -invitation.invite.not_site_manager="Current user, {0}, is not a Site Manager for site: {1} +invitation.cancel.not_site_manager="Current user, {0}, cannot cancel invitation: {1} because they are not a Site Manager for site: {2}" +invitation.invite.not_site_manager="Current user, {0}, is not a Site Manager for site: {1}" invitation.invite.unable_generate_id="Unable to generate a user name for invitee, which doesn't already belong to someone else firstName:{0} lastName:{1} email:{2}" invitation.invite.already_finished="Invitation, {0} has already been accepted, cancelled or rejected" invitation.invite.authentication_chain="Authentication chain does not allow creation of new accounts" # InviteSender messages -invitation.invitesender.email.subject=Invitation to join {0} site +invitation.invitesender.email.subject=Alfresco {0}: You have been invited to join the {1} site invitation.invitesender.email.role.SiteManager=Site Manager invitation.invitesender.email.role.SiteCollaborator=Site Collaborator invitation.invitesender.email.role.SiteContributor=Site Contributor @@ -21,4 +21,7 @@ invitation.invitesender.email.role.SiteConsumer=Site Consumer # Invitation workflow task description invitation.nominated.workflow.description=Invitation to join {0} site -invitation.moderated.workflow.description=Request to join {0} site \ No newline at end of file +invitation.moderated.workflow.description=Request to join {0} site + +# Person Notification messages +invitation.notification.person.email.subject=Alfresco {0}: Your New Account diff --git a/config/alfresco/messages/invitation-service_de.properties b/config/alfresco/messages/invitation-service_de.properties index ba34befea0..6755650c59 100755 --- a/config/alfresco/messages/invitation-service_de.properties +++ b/config/alfresco/messages/invitation-service_de.properties @@ -2,23 +2,26 @@ # Invitation service messages # -invitation.error.noworkflow=Einladungsworkflow wurde nicht gefunden, Name des Workflows: {0} -invitation.error.not_found=Einladung nicht gefunden, Einladungs-ID: {0} -invitation.error.invalid_inviteId_format=Einladungs-ID hat ung\u00fcltiges Format, g\u00fcltige Formate sind $: {0} -invitation.invite.already_member=Der Benutzer {0} ist bereits Mitglied von {1} und kann nicht noch einmal eingeladen werden -invitation.cancel.not_site_manager=Aktueller Benutzer {0} kann Einladung: {1} nicht abbrechen, da sie nicht Site Manger f\u00fcr Site: {2} sind -invitation.invite.not_site_manager="Aktueller Benutzer {0} ist kein Site Manager f\u00fcr Site: {1} -invitation.invite.unable_generate_id=Kann keinen Benutzernamen f\u00fcr Eingeladenen generieren, der nicht schon jemand anderem geh\u00f6rt Vorname:{0} Nachname: {1} E-Mail:{2} +invitation.error.noworkflow="Einladungsworkflow wurde nicht gefunden, Name des Workflows: {0}" +invitation.error.not_found="Einladung nicht gefunden, Einladungs-ID: {0}" +invitation.error.invalid_inviteId_format="Einladungs-ID hat ung\u00fcltiges Format, g\u00fcltige Formate sind $: {0}" +invitation.invite.already_member="Der Benutzer {0} ist bereits Mitglied von {1} und kann nicht noch einmal eingeladen werden" +invitation.cancel.not_site_manager="Aktueller Benutzer {0} kann Einladung: {1} nicht abbrechen, da sie nicht Site Manger f\u00fcr Site: {2} sind" +invitation.invite.not_site_manager="Aktueller Benutzer {0} ist kein Site Manager f\u00fcr Site: {1}" +invitation.invite.unable_generate_id="Kann keinen Benutzernamen f\u00fcr Eingeladenen generieren, der nicht schon jemand anderem geh\u00f6rt Vorname:{0} Nachname: {1} E-Mail:{2}" invitation.invite.already_finished="Einladung {0} wurde bereits angenommen, abgebrochen oder abgelehnt" invitation.invite.authentication_chain="Authentifizierungskette l\u00e4sst das Erstellen neuer Konten nicht zu" # InviteSender messages -invitation.invitesender.email.subject=Einladung, der Site {0} beizutreten +invitation.invitesender.email.subject=Alfresco {0}: Sie wurden aufgefordert, der {1} Site beizutreten invitation.invitesender.email.role.SiteManager=Site Manager invitation.invitesender.email.role.SiteCollaborator=Site Mitarbeiter invitation.invitesender.email.role.SiteContributor=Site Beitragender -invitation.invitesender.email.role.SiteConsumer=Site Konsument +invitation.invitesender.email.role.SiteConsumer=Site Verbraucher # Invitation workflow task description invitation.nominated.workflow.description=Einladung, der Site {0} beizutreten invitation.moderated.workflow.description=Anfrage, der Site {0} beizutreten + +# Person Notification messages +invitation.notification.person.email.subject=Alfresco {0}: Ihr neues Konto diff --git a/config/alfresco/messages/invitation-service_es.properties b/config/alfresco/messages/invitation-service_es.properties index dea5735b36..64081158c6 100755 --- a/config/alfresco/messages/invitation-service_es.properties +++ b/config/alfresco/messages/invitation-service_es.properties @@ -2,18 +2,18 @@ # Invitation service messages # -invitation.error.noworkflow=No se encuentra el flujo de trabajo de invitaci\u00f3n, nombre de flujo de trabajo: {0} -invitation.error.not_found=Invitaci\u00f3n no encontrada, invitationId: {0} -invitation.error.invalid_inviteId_format=Formato del Id de invitaci\u00f3n inv\u00e1lido, los formatos v\u00e1lidos son $: {0} -invitation.invite.already_member=El usuario {0} ya es un miembro de {1} y no puede ser invitado de nuevo -invitation.cancel.not_site_manager=El usuario actual, {0}, no puede cancelar la invitaci\u00f3n: {1} porque no es administrador del sitio en el siguiente sitio: {2} -invitation.invite.not_site_manager=El usuario actual, {0}, no es administrador del sitio para el siguiente sitio: {1} -invitation.invite.unable_generate_id=No se puede generar un nombre de usuario para el invitado, que contenga Nombre:{0} Apellidos:{1} correo electr\u00f3nico:{2} de otra persona +invitation.error.noworkflow="No se encuentra el flujo de trabajo de invitaci\u00f3n, nombre de flujo de trabajo: {0}" +invitation.error.not_found="Invitaci\u00f3n no encontrada, invitationId: {0}" +invitation.error.invalid_inviteId_format="Formato del Id de invitaci\u00f3n inv\u00e1lido, los formatos v\u00e1lidos son $: {0}" +invitation.invite.already_member="El usuario {0} ya es un miembro de {1} y no puede ser invitado de nuevo" +invitation.cancel.not_site_manager="El usuario actual, {0}, no puede cancelar la invitaci\u00f3n: {1} porque no es administrador del sitio en el siguiente sitio: {2}" +invitation.invite.not_site_manager="El usuario actual, {0}, no es administrador del sitio para el siguiente sitio: {1}" +invitation.invite.unable_generate_id="No se puede generar un nombre de usuario para el invitado, que contenga Nombre:{0} Apellidos:{1} correo electr\u00f3nico:{2} de otra persona" invitation.invite.already_finished="La invitaci\u00f3n, {0} ya ha sido aceptada, cancelada o rechazada" invitation.invite.authentication_chain="El m\u00e9todo de autenticaci\u00f3n establecido no permite crear nuevas cuentas" # InviteSender messages -invitation.invitesender.email.subject=Invitaci\u00f3n a unirse al sitio {0} +invitation.invitesender.email.subject={0} Alfresco: Se le ha invitado a unirse al sitio {1} invitation.invitesender.email.role.SiteManager=Administrador de sitio invitation.invitesender.email.role.SiteCollaborator=Colaborador de sitio invitation.invitesender.email.role.SiteContributor=Contribuidor de sitio @@ -22,3 +22,6 @@ invitation.invitesender.email.role.SiteConsumer=Consumidor de sitio # Invitation workflow task description invitation.nominated.workflow.description=Invitaci\u00f3n a unirse al sitio {0} invitation.moderated.workflow.description=Solicitud de unirse al sitio {0} + +# Person Notification messages +invitation.notification.person.email.subject={0} Alfresco: Su nueva cuenta diff --git a/config/alfresco/messages/invitation-service_fr.properties b/config/alfresco/messages/invitation-service_fr.properties index 73aff2b903..8b2ebf946d 100755 --- a/config/alfresco/messages/invitation-service_fr.properties +++ b/config/alfresco/messages/invitation-service_fr.properties @@ -2,18 +2,18 @@ # Invitation service messages # -invitation.error.noworkflow =Flux de travail d''invitation introuvable, nom du flux de travail\u00a0: {0} -invitation.error.not_found =Invitation introuvable, identifiant de l''invitation\u00a0: {0} -invitation.error.invalid_inviteId_format =Format de l''identifiant de l''invitation invalide, les formats valides sont $\u00a0: {0} -invitation.invite.already_member =L''utilisateur {0} est d\u00e9j\u00e0 membre de {1} et ne peut pas \u00eatre \u00e0 nouveau invit\u00e9 -invitation.cancel.not_site_manager =L''utilisateur actuel {0}ne peut pas annuler l''invitation suivante\u00a0: {1}, car il n''est pas un gestionnaire du site suivant\u00a0: {2} -invitation.invite.not_site_manager= ''L''utilisateur actuel {0} n''est pas un gestionnaire du site suivant\u00a0: {1} -invitation.invite.unable_generate_id =Impossible de g\u00e9n\u00e9rer un nom d''utilisateur pour l''invit\u00e9 qui n''appartient pas d\u00e9j\u00e0 \u00e0 une autre personne firstName:{0} lastName:{1} email:{2} -invitation.invite.already_finished= ''L''invitation {0} a d\u00e9j\u00e0 \u00e9t\u00e9 accept\u00e9e, annul\u00e9e ou rejet\u00e9e'' -invitation.invite.authentication_chain= ''La cha\u00eene d'authentification ne permet pas la cr\u00e9ation de nouveaux comptes'' +invitation.error.noworkflow="Workflow d''invitation introuvable, nom du workflow : {0}" +invitation.error.not_found="Invitation introuvable, identifiant de l''invitation : {0}" +invitation.error.invalid_inviteId_format="Format de l''identifiant de l''invitation non valide, les formats valides sont $ : {0}" +invitation.invite.already_member="L''utilisateur {0} est d\u00e9j\u00e0 membre de {1} et ne peut pas \u00eatre \u00e0 nouveau invit\u00e9" +invitation.cancel.not_site_manager="L''utilisateur actuel {0} ne peut pas annuler l''invitation suivante : {1}, car il n''est pas un gestionnaire du site suivant : {2}" +invitation.invite.not_site_manager="L''utilisateur actuel {0} n''est pas un gestionnaire du site suivant : {1}" +invitation.invite.unable_generate_id="Impossible de g\u00e9n\u00e9rer un nom d''utilisateur pour l''invit\u00e9, qui n''appartient pas d\u00e9j\u00e0 \u00e0 une autre personne firstName:{0} lastName:{1} email:{2}" +invitation.invite.already_finished="L''invitation {0} a d\u00e9j\u00e0 \u00e9t\u00e9 accept\u00e9e, annul\u00e9e ou rejet\u00e9e" +invitation.invite.authentication_chain="La cha\u00eene d'authentification ne permet pas la cr\u00e9ation de nouveaux comptes" # InviteSender messages -invitation.invitesender.email.subject=Invitation \u00e0 rejoindre le site {0} +invitation.invitesender.email.subject=Alfresco {0} : Vous avez \u00e9t\u00e9 invit\u00e9 \u00e0 rejoindre le site {1} invitation.invitesender.email.role.SiteManager=Gestionnaire du site invitation.invitesender.email.role.SiteCollaborator=Collaborateur du site invitation.invitesender.email.role.SiteContributor=Contributeur du site @@ -22,3 +22,6 @@ invitation.invitesender.email.role.SiteConsumer=Visiteur du site # Invitation workflow task description invitation.nominated.workflow.description=Invitation \u00e0 rejoindre le site {0} invitation.moderated.workflow.description=Requ\u00eate pour rejoindre le site {0} + +# Person Notification messages +invitation.notification.person.email.subject=Alfresco {0} : Votre nouveau compte diff --git a/config/alfresco/messages/invitation-service_it.properties b/config/alfresco/messages/invitation-service_it.properties index cce4c838f7..42ce52c532 100755 --- a/config/alfresco/messages/invitation-service_it.properties +++ b/config/alfresco/messages/invitation-service_it.properties @@ -2,23 +2,26 @@ # Invitation service messages # -invitation.error.noworkflow=Impossibile trovare il workflow di invito con nome: {0} -invitation.error.not_found=Impossibile trovare l'invito con ID: {0} -invitation.error.invalid_inviteId_format=Il formato dell'ID di invito non \u00e8 valido. I formati validi sono $: {0} -invitation.invite.already_member=L'utente {0} \u00e8 gi\u00e0 un membro di {1} e non pu\u00f2 essere invitato di nuovo -invitation.cancel.not_site_manager=L'utente attuale {0} non pu\u00f2 annullare l'invito: {1} perch\u00e9 non \u00e8 un manager del sito: {2} -invitation.invite.not_site_manager="L'utente attuale {0} non \u00e8 un manager del sito: {1} -invitation.invite.unable_generate_id=Impossibile generare per l'invitato un nome utente che non appartiene gi\u00e0 a un altro utente firstName:{0} lastName:{1} email:{2} -invitation.invite.already_finished="L'invito {0} \u00e8 gi\u00e0 stato accettato, annullato o respinto" +invitation.error.noworkflow="Impossibile trovare il workflow di invito con nome: {0}" +invitation.error.not_found="Impossibile trovare l''invito con ID: {0}" +invitation.error.invalid_inviteId_format="Il formato dell''ID di invito non \u00e8 valido, i formati validi sono $ : {0}" +invitation.invite.already_member="L''utente, {0} \u00e8 gi\u00e0 un membro di {1} e non pu\u00f2 essere invitato di nuovo" +invitation.cancel.not_site_manager="L''utente attuale, {0}, non pu\u00f2 annullare l''invito: {1} perch\u00e9 non \u00e8 un manager del sito: {2}" +invitation.invite.not_site_manager="L''utente attuale, {0}, non \u00e8 un manager del sito: {1}" +invitation.invite.unable_generate_id="Impossibile generare per l'invitato un nome, utente che non appartiene gi\u00e0 a un altro utente firstName:{0} lastName:{1} email:{2}" +invitation.invite.already_finished="L''invito, {0} \u00e8 gi\u00e0 stato accettato, annullato o respinto" invitation.invite.authentication_chain="La catena di autenticazione non consente la creazione di nuovi account" # InviteSender messages -invitation.invitesender.email.subject=Invito al sito {0} +invitation.invitesender.email.subject=Alfresco {0}: \u00e8 stato ricevuto un invito a partecipare al sito {1} invitation.invitesender.email.role.SiteManager=Manager sito invitation.invitesender.email.role.SiteCollaborator=Collaboratore sito invitation.invitesender.email.role.SiteContributor=Contributore sito invitation.invitesender.email.role.SiteConsumer=Consumatore sito # Invitation workflow task description -invitation.nominated.workflow.description=Invito a partecipare al sito {0} -invitation.moderated.workflow.description=Richiesta di partecipare al sito {0} +invitation.nominated.workflow.description=Invito a partecipare al {0} sito +invitation.moderated.workflow.description=Richiesta di partecipare al {0} sito + +# Person Notification messages +invitation.notification.person.email.subject=Alfresco {0}: nuovo account diff --git a/config/alfresco/messages/invitation-service_ja.properties b/config/alfresco/messages/invitation-service_ja.properties new file mode 100755 index 0000000000..af89142b67 --- /dev/null +++ b/config/alfresco/messages/invitation-service_ja.properties @@ -0,0 +1,27 @@ +# +# Invitation service messages +# + +invitation.error.noworkflow="\u62db\u5f85\u306e\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u540d: {0}" +invitation.error.not_found="\u62db\u5f85\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u62db\u5f85ID: {0}" +invitation.error.invalid_inviteId_format="\u62db\u5f85ID\u304c\u7121\u52b9\u306a\u5f62\u5f0f\u3067\u3059\u3002\u6709\u52b9\u306a\u5f62\u5f0f\u306f$ : {0}\u3067\u3059" +invitation.invite.already_member="\u30e6\u30fc\u30b6{0}\u306f\u3059\u3067\u306b{1}\u306e\u30e1\u30f3\u30d0\u3067\u3059\u3002\u3082\u3046\u4e00\u5ea6\u62db\u5f85\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093" +invitation.cancel.not_site_manager="\u73fe\u5728\u306e\u30e6\u30fc\u30b6{0}\u306f\u3001\u62db\u5f85{1}\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3067\u304d\u307e\u305b\u3093\u3002\u30b5\u30a4\u30c8{2}\u306e\u30b5\u30a4\u30c8\u30fb\u30de\u30cd\u30fc\u30b8\u30e3\u3067\u306f\u3042\u308a\u307e\u305b\u3093" +invitation.invite.not_site_manager="\u73fe\u5728\u306e\u30e6\u30fc\u30b6{0}\u306f\u3001\u30b5\u30a4\u30c8{1}\u306e\u30b5\u30a4\u30c8\u30fb\u30de\u30cd\u30fc\u30b8\u30e3\u3067\u306f\u3042\u308a\u307e\u305b\u3093" +invitation.invite.unable_generate_id="\u88ab\u62db\u5f85\u8005\u306e\u30e6\u30fc\u30b6\u540d\u3092\u751f\u6210\u3067\u304d\u307e\u305b\u3093\u3002\u540d: {0}\u3001\u59d3: {1}\u3001E\u30e1\u30fc\u30eb: {2}\u3068\u3044\u3046\u4ed6\u306e\u4eba\u306b\u307e\u3060\u5c5e\u3057\u3066\u3044\u307e\u305b\u3093" +invitation.invite.already_finished="\u62db\u5f85{0}\u306f\u3001\u3059\u3067\u306b\u627f\u8afe\u3001\u30ad\u30e3\u30f3\u30bb\u30eb\u3001\u307e\u305f\u306f\u5374\u4e0b\u3055\u308c\u3066\u3044\u307e\u3059" +invitation.invite.authentication_chain="\u8a8d\u8a3c\u9023\u9396\u306b\u3088\u308a\u3001\u65b0\u3057\u3044\u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u4f5c\u6210\u304c\u8a31\u53ef\u3055\u308c\u3066\u3044\u307e\u305b\u3093" + +# InviteSender messages +invitation.invitesender.email.subject=Alfresco {0}: \u3042\u306a\u305f\u306f\u3001{1}\u30b5\u30a4\u30c8\u3078\u306e\u53c2\u52a0\u306b\u62db\u5f85\u3055\u308c\u3066\u3044\u307e\u3059\u3002 +invitation.invitesender.email.role.SiteManager=\u30b5\u30a4\u30c8\u30fb\u30de\u30cd\u30fc\u30b8\u30e3 +invitation.invitesender.email.role.SiteCollaborator=\u30b5\u30a4\u30c8\u5171\u540c\u4f5c\u696d\u8005 +invitation.invitesender.email.role.SiteContributor=\u30b5\u30a4\u30c8\u30fb\u30b3\u30f3\u30c8\u30ea\u30d3\u30e5\u30fc\u30bf +invitation.invitesender.email.role.SiteConsumer=\u30b5\u30a4\u30c8\u30fb\u30b2\u30b9\u30c8 + +# Invitation workflow task description +invitation.nominated.workflow.description={0}\u30b5\u30a4\u30c8\u3078\u306e\u53c2\u52a0\u306e\u62db\u5f85 +invitation.moderated.workflow.description={0}\u30b5\u30a4\u30c8\u3078\u306e\u53c2\u52a0\u306e\u8981\u6c42 + +# Person Notification messages +invitation.notification.person.email.subject=Alfresco {0}: \u65b0\u3057\u3044\u30a2\u30ab\u30a6\u30f3\u30c8 diff --git a/config/alfresco/messages/jbpm-engine-messages_de.properties b/config/alfresco/messages/jbpm-engine-messages_de.properties index ce02277a05..e0b25150cf 100755 --- a/config/alfresco/messages/jbpm-engine-messages_de.properties +++ b/config/alfresco/messages/jbpm-engine-messages_de.properties @@ -42,4 +42,4 @@ jbpm.engine.set.task.properties.invalid.value=The value {0} is invalid for the t jbpm.engine.package.already.associated.error=Cannot associate workflow package {0} with workflow instance {1} as its already associated with workflow instance {2}". jbpm.engine.convert.value.error=Unable to convert jBPM value {0} to Alfresco Value since it is not Serializable. jbpm.engine.get.company.home.invalid=Invalid company home path {0}. -jbpm.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. \ No newline at end of file +jbpm.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. diff --git a/config/alfresco/messages/jbpm-engine-messages_es.properties b/config/alfresco/messages/jbpm-engine-messages_es.properties index ce02277a05..e0b25150cf 100755 --- a/config/alfresco/messages/jbpm-engine-messages_es.properties +++ b/config/alfresco/messages/jbpm-engine-messages_es.properties @@ -42,4 +42,4 @@ jbpm.engine.set.task.properties.invalid.value=The value {0} is invalid for the t jbpm.engine.package.already.associated.error=Cannot associate workflow package {0} with workflow instance {1} as its already associated with workflow instance {2}". jbpm.engine.convert.value.error=Unable to convert jBPM value {0} to Alfresco Value since it is not Serializable. jbpm.engine.get.company.home.invalid=Invalid company home path {0}. -jbpm.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. \ No newline at end of file +jbpm.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. diff --git a/config/alfresco/messages/jbpm-engine-messages_fr.properties b/config/alfresco/messages/jbpm-engine-messages_fr.properties index ce02277a05..e0b25150cf 100755 --- a/config/alfresco/messages/jbpm-engine-messages_fr.properties +++ b/config/alfresco/messages/jbpm-engine-messages_fr.properties @@ -42,4 +42,4 @@ jbpm.engine.set.task.properties.invalid.value=The value {0} is invalid for the t jbpm.engine.package.already.associated.error=Cannot associate workflow package {0} with workflow instance {1} as its already associated with workflow instance {2}". jbpm.engine.convert.value.error=Unable to convert jBPM value {0} to Alfresco Value since it is not Serializable. jbpm.engine.get.company.home.invalid=Invalid company home path {0}. -jbpm.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. \ No newline at end of file +jbpm.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. diff --git a/config/alfresco/messages/jbpm-engine-messages_it.properties b/config/alfresco/messages/jbpm-engine-messages_it.properties index ce02277a05..e0b25150cf 100755 --- a/config/alfresco/messages/jbpm-engine-messages_it.properties +++ b/config/alfresco/messages/jbpm-engine-messages_it.properties @@ -42,4 +42,4 @@ jbpm.engine.set.task.properties.invalid.value=The value {0} is invalid for the t jbpm.engine.package.already.associated.error=Cannot associate workflow package {0} with workflow instance {1} as its already associated with workflow instance {2}". jbpm.engine.convert.value.error=Unable to convert jBPM value {0} to Alfresco Value since it is not Serializable. jbpm.engine.get.company.home.invalid=Invalid company home path {0}. -jbpm.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. \ No newline at end of file +jbpm.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. diff --git a/config/alfresco/messages/jbpm-engine-messages_ja.properties b/config/alfresco/messages/jbpm-engine-messages_ja.properties new file mode 100755 index 0000000000..e0b25150cf --- /dev/null +++ b/config/alfresco/messages/jbpm-engine-messages_ja.properties @@ -0,0 +1,45 @@ +Jbpm.engine.mandatory.properties.missing=Mandatory task properties have not been provided! {0} +jbpm.engine.deploy.workflow.error=Failed to deploy workflow definition. +jbpm.engine.is.workflow.deployed.error=Failed to determine if workflow definition is already deployed. +jbpm.engine.undeploy.workflow.error=Failed to undeploy workflow definition {0}. +jbpm.engine.get.workflow.definition.error=Failed to retrieve workflow definitions. +jbpm.engine.get.workflow.definition.by.id.error=Failed to retrieve workflow definition for id {0}. +jbpm.engine.get.workflow.definition.by.name.error=Failed to retrieve workflow definition for name {0}. +jbpm.engine.get.all.workflow.definitions.by.name.error=Failed to retrieve all definitions for workflow {0}. +jbpm.engine.get.workflow.definition.image.error=Failed to retrieve workflow definition image for {0}. +jbpm.engine.get.task.definitions.error=Failed to retrieve workflow task definitions for workflow definition {0}. +jbpm.engine.get.process.definition.error=Workflow definition {0} does not exist. +jbpm.enginestart.workflow.error=Failed to start workflow {0}. +jbpm.engine.get.active.workflows.error=Failed to retrieve workflow instances for definition {0}. +jbpm.engine.get.workflow.instance.by.id.error=Failed to retrieve workflow instance with Id {0}. +jbpm.engine.get.process.instance.error=Workflow instance does not exist for Id {0}. +jbpm.engine.get.workflow.paths.error=Failed to retrieve workflow paths for workflow instance {0}. +jbpm.engine.get.path.properties.error=Failed to retrieve properties of path {0}. +jbpm.engine.cancel.workflow.error=Failed to cancel workflow instance {0}. +jbpm.engine.delete.workflow.error=Failed to delete workflow instance {0}. +jbpm.engine.signal.transition.error=Failed to signal transition {0} from workflow path {1}.. +jbpm.engine.invalid.event=Event {0} is not a valid event, this event name is reserved. +jbpm.engine.fire.event.error=Failed to fire event {0} on workflow path {1}. +jbpm.engine.get.tasks.for.path.error=Failed to retrieve tasks assigned to Workflow path {0}. +jbpm.engine.get.timers.error=Could not retrieve any timers for workflow {0}. +jbpm.engine.find.completed.task.instances.error=Failed to retrieve completed task instances list for actor {0}. +jbpm.engine.get.assigned.tasks.error=Failed to retrieve tasks assigned to authority {0} in state {1}. +jbpm.engine.get.pooled.tasks.error=Failed to retrieve pooled tasks for authorities {0}. +jbpm.engine.query.tasks.error=Failed to query tasks. Query: {0}. +jbpm.engine.get.task.instance.error=Task instance {0} does not exist. +jbpm.engine.update.task.error=Failed to update workflow task {0}. +jbpm.engine.end.task.invalid.transition=Transition {0} is invalid for Workflow task {1}. +jbpm.engine.end.task.error=Failed to signal transition {0} from workflow task {1}. +jbpm.engine.get.task.by.id.error=Failed to retrieve task {0}. +jbpm.engine.compile.process.definition.zip.error=Failed to parse process definition from jBPM zip archive stream. +jbpm.engine.compile.process.definition.xml.error=Failed to parse process definition from jBPM xml stream. +jbpm.engine.compile.process.definition.unsupported.error=Failed to parse process definition - unsupported mime type {0} +jbpm.engine.get.task.definition.error=Failed to find type definition {0}. +jbpm.engine.get.jbpm.id.error=Format of id {0} is invalid. +jbpm.engine.get.workflow.token.invalid=Invalid workflow path {0}. +jbpm.engine.get.workflow.token.is.null=Workflow path {0} does not exist. +jbpm.engine.set.task.properties.invalid.value=The value {0} is invalid for the task property {1}. +jbpm.engine.package.already.associated.error=Cannot associate workflow package {0} with workflow instance {1} as its already associated with workflow instance {2}". +jbpm.engine.convert.value.error=Unable to convert jBPM value {0} to Alfresco Value since it is not Serializable. +jbpm.engine.get.company.home.invalid=Invalid company home path {0}. +jbpm.engine.get.company.home.multiple=Invalid company home path {0}. Expected 1 match but found {1} matches. diff --git a/config/alfresco/messages/lock-service_fr.properties b/config/alfresco/messages/lock-service_fr.properties index d81fe5ceda..238c227104 100755 --- a/config/alfresco/messages/lock-service_fr.properties +++ b/config/alfresco/messages/lock-service_fr.properties @@ -2,5 +2,5 @@ lock_service.insufficent_privileges=Vos privil\u00e8ges sont insuffisants pour d\u00e9verrouiller le n\u009cud (identifiant\u00a0: {0}). Le noeud est verrouill\u00e9 par un autre utilisateur. lock_service.node_locked=Le n\u009cud (identifiant\u00a0: {0}) n''a pas pu \u00eatre verrouill\u00e9 car il est d\u00e9j\u00e0 verrouill\u00e9 par un autre utilisateur. -lock_service.no_op=Impossible d''ex\u00e9cuter l''op\u00e9ration car le n\u009cud (identifiant\u00a0: {0}) est verrouill\u00e9. +lock_service.no_op=Impossible de r\u00e9aliser l''op\u00e9ration car le n\u009cud (identifiant\u00a0: {0}) est verrouill\u00e9. lock_service.no_op2=Impossible de r\u00e9aliser l''op\u00e9ration {0} car le noeud (id : {1}) est verrouill\u00e9. diff --git a/config/alfresco/messages/lock-service_it.properties b/config/alfresco/messages/lock-service_it.properties index c013420393..217882fce8 100755 --- a/config/alfresco/messages/lock-service_it.properties +++ b/config/alfresco/messages/lock-service_it.properties @@ -1,6 +1,6 @@ # Lock service externalised display strings -lock_service.insufficent_privileges=Privilegi insufficienti per rilasciare il blocco del nodo (ID: {0}). Il nodo \u00e8 bloccato da un altro utente. -lock_service.node_locked=Impossibile bloccare il nodo (ID: {0}) perch\u00e9 \u00e8 gi\u00e0 bloccato da un altro utente. -lock_service.no_op=Impossibile eseguire l'operazione perch\u00e9 il nodo (ID:{0}) \u00e8 bloccato. -lock_service.no_op2=Impossibile eseguire l'operazione {0} perch\u00e9 il nodo (ID:{1}) \u00e8 bloccato. +lock_service.insufficent_privileges=Privilegi insufficienti per rilasciare il blocco del nodo (id: {0}). Il nodo \u00e8 bloccato da un altro utente. +lock_service.node_locked=Impossibile bloccare il nodo (id: {0}) perch\u00e9 \u00e8 gi\u00e0 bloccato da un altro utente. +lock_service.no_op=Impossibile eseguire l'operazione perch\u00e9 il nodo (id:{0}) \u00e8 bloccato. +lock_service.no_op2=Impossibile eseguire l'operazione {0} perch\u00e9 il nodo (id:{1}) \u00e8 bloccato. diff --git a/config/alfresco/messages/lock-service_ja.properties b/config/alfresco/messages/lock-service_ja.properties new file mode 100755 index 0000000000..be9b2736af --- /dev/null +++ b/config/alfresco/messages/lock-service_ja.properties @@ -0,0 +1,6 @@ +# Lock service externalised display strings + +lock_service.insufficent_privileges=\u3053\u306e\u30ce\u30fc\u30c9(id: {0})\u306e\u30ed\u30c3\u30af\u3092\u89e3\u9664\u3059\u308b\u305f\u3081\u306e\u5341\u5206\u306a\u6a29\u9650\u304c\u3042\u308a\u307e\u305b\u3093\u3002 \u3053\u306e\u30ce\u30fc\u30c9\u306f\u4ed6\u306e\u30e6\u30fc\u30b6\u306b\u3088\u3063\u3066\u30ed\u30c3\u30af\u3055\u308c\u3066\u3044\u307e\u3059\u3002 +lock_service.node_locked=\u3053\u306e\u30ce\u30fc\u30c9(id: {0}) \u306f\u4ed6\u306e\u30e6\u30fc\u30b6\u306b\u3088\u3063\u3066\u3059\u3067\u306b\u30ed\u30c3\u30af\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30ed\u30c3\u30af\u3067\u304d\u307e\u305b\u3093\u3002 +lock_service.no_op=\u30ce\u30fc\u30c9(id:{0})\u304c\u30ed\u30c3\u30af\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30aa\u30da\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002 +lock_service.no_op2=\u30ce\u30fc\u30c9(id:{1})\u304c\u30ed\u30c3\u30af\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30aa\u30da\u30ec\u30fc\u30b7\u30e7\u30f3 {0} \u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3002 diff --git a/config/alfresco/messages/module-messages_ja.properties b/config/alfresco/messages/module-messages_ja.properties new file mode 100755 index 0000000000..930ad2a92e --- /dev/null +++ b/config/alfresco/messages/module-messages_ja.properties @@ -0,0 +1,20 @@ +# Module messages + +module.msg.found_modules=Found {0} module(s). +module.msg.starting= Starting module ''{0}'' version {1}. +module.msg.installing= Installing module ''{0}'' version {1}. +module.msg.upgrading= Upgrading module ''{0}'' version {1} (was {2}). +module.msg.missing= A previously-installed module ''{0}'' (version {1}) is not present in your distribution. +module.msg.dependencies= Module ''{0}'' version {1} has the following dependencies: {1} + +module.warn.no_install_version=Module ''{0}'' had no install version. Assuming version {1} was installed. + +module.err.missing_dependency=\nModule ''{0}'' version {1} depends on module ''{2}'', which has not been installed. +module.err.downgrading_not_supported=\nDowngrading of modules is not supported.\nModule ''{0}'' version {1} is currently installed and must be uninstalled before version {2} can be installed. +module.err.unsupported_repo_version=\nModule ''{0}'' version {1} is incompatible with the current repository version {2}.\n The repository version required must be in range [{3} : {4}]. +module.err.already_executed=The module component has already been executed: {0}.{1} +module.err.execution_failed=A module component ''{0}'' failed to execute: {1} +module.err.component_already_registered=A component named ''{0}'' has already been registered for module ''{1}''. +module.err.unable_to_open_module_properties=The module properties file ''{0}'' could not be read. +module.err.component_in_missing_module=The component ''{0}'' belongs to a non-existent module ''{1}''. +module.err.orphaned_components={0} module components were not considered for execution. diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 06e5f9b670..906ba4a08d 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -148,6 +148,11 @@ patch.versionHistoryPerformance.result=Updated {0} version history objects to im patch.multilingualBootstrap.description=Bootstraps the node that will hold the multilingual containers. +patch.siteLoadPatch.description=Loads a sample site into the repository. +patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported +patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration +patch.siteLoadPatch.result=Site {0} imported. + patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} patch.wcmFolders.webprojects.result.created=The Web Projects folder was successfully created: {0} @@ -383,3 +388,15 @@ patch.mtFixAdminExistingTenants.result=Fix bootstrapped creator/modifier patch.fixUserQNames.description=Fixes user store qnames to improve native authentication performance patch.fixUserQNames.result=Generated qnames for {0} users. + +patch.newUserEmailTemplates.description=Adds the email templates for notifying new users of their accounts +patch.newUserEmailTemplates.created=Email templates added: {0} + +patch.inviteEmailTemplates.description=Adds the email templates for inviting users to a Site +patch.inviteEmailTemplates.created=Email templates added: {0} + +patch.htmlNotificationMailTemplates.description=Adds HTML email templates for notifying users of new content + +patch.imapSpacesLocaleTemplates.description=Adds content templates for IMAP clients (Non-default locales only) + +patch.exampleJavaScript.description=Loads sample Javascript file into datadictionary scripts folder \ No newline at end of file diff --git a/config/alfresco/messages/patch-service_de.properties b/config/alfresco/messages/patch-service_de.properties index 10568e7239..bb49df60e8 100755 --- a/config/alfresco/messages/patch-service_de.properties +++ b/config/alfresco/messages/patch-service_de.properties @@ -45,7 +45,7 @@ patch.savedSearchesPermission.result.applied=Granted CONTRIBUTOR role to EVERYON patch.savedSearchesPermission.err.not_found='Saved Searches' folder could not be found. patch.updatePermissionData.description=Update permissions from 'folder' to 'cmobject' [JIRA: AR-344]. -patch.updatePermissionData.result=Changed {0} ''folder'' access control entries to ''cmobject''. +patch.updatePermissionData.result=Changed {0} 'folder' access control entries to 'cmobject'. patch.authoritiesFolder.description=Ensures the existence of the user authorities folder [JIRA: AR-497]. @@ -58,11 +58,11 @@ patch.fixNodeSerializableValues.description=Ensure that property values are not patch.fixNodeSerializableValues.result=Fixed {0} node property serialized values patch.updateGuestPermission.description=Rename guest permission from 'Guest' to 'Consumer' -patch.updateGuestPermission.result=Changed {0} ''Guest'' access control entries to ''Consumer''. +patch.updateGuestPermission.result=Changed {0} 'Guest' access control entries to 'Consumer'. patch.categoryRootPermission.description=Sets required permissions on 'Category Root' folder. -patch.categoryRootPermission.result=Granted CONSUMER role to GUEST on ''Category Root'' folder: {0}. -patch.categoryRootPermission.err.not_found=''Category Root'' folder ({0}) could not be found. +patch.categoryRootPermission.result=Granted CONSUMER role to GUEST on 'Category Root' folder: {0}. +patch.categoryRootPermission.err.not_found='Category Root' folder ({0}) could not be found. patch.guestPersonPermission.description=Change Guest Person permission from 'Consumer' to 'Read' patch.guestPersonPermission.result=Updated Guest Person permission from 'Consumer' to 'Read' @@ -71,7 +71,7 @@ patch.spacesRootPermission.description=Change Spaces store root permission from patch.spacesRootPermission.result=Updated Spaces store root permission from 'Consumer' to 'Read' patch.contentPermission.description=Update permission entries from 'cm:content' to 'sys:base'. -patch.contentPermission.result=Changed {0} ''cm:content'' access control entries to ''sys:base''. +patch.contentPermission.result=Changed {0} 'cm:content' access control entries to 'sys:base'. patch.forumsIcons.description=Updates forums icon references patch.forumsIcons.result=Updated {0} icon references @@ -148,6 +148,11 @@ patch.versionHistoryPerformance.result=Updated {0} version history objects to im patch.multilingualBootstrap.description=Bootstraps the node that will hold the multilingual containers. +patch.siteLoadPatch.description=Loads a sample site into the repository. +patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported +patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration +patch.siteLoadPatch.result=Site {0} imported. + patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} patch.wcmFolders.webprojects.result.created=The Web Projects folder was successfully created: {0} @@ -227,7 +232,7 @@ patch.deploymentMigration.reportMigrated=Deployment report for ''{0}'' from web patch.deploymentMigration.result=Deployment data has been migrated. patch.updateAvmPermissionData.description=Update avm permissions from 'webfolder' to 'cmobject'. -patch.updateAvmPermissionData.result=Changed {0} ''webfolder'' access control entries to ''cmobject''. +patch.updateAvmPermissionData.result=Changed {0} 'webfolder' access control entries to 'cmobject'. patch.updateAvmPermissions.description=Update ACLs on all avm objects to the new 2.2 permission model patch.updateAvmPermissions.result=Updated ACLs. Created {0} defining and {1} layered ACLs. @@ -377,3 +382,21 @@ patch.transfer.targetrule.description=Creates the transfer target rule for the d patch.actions.scheduledfolder.description=Creates the scheduled actions folder in the Data Dictionary. patch.removingLinkValidationMetadata.description=Fixes ALF-5185: Removes all Link Validation reports from schema + +patch.mtFixAdminExistingTenants.description=Fix bootstrapped creator/modifier +patch.mtFixAdminExistingTenants.result=Fix bootstrapped creator/modifier + +patch.fixUserQNames.description=Fixes user store qnames to improve native authentication performance +patch.fixUserQNames.result=Generated qnames for {0} users. + +patch.newUserEmailTemplates.description=Adds the email templates for notifying new users of their accounts +patch.newUserEmailTemplates.created=Email templates added: {0} + +patch.inviteEmailTemplates.description=Adds the email templates for inviting users to a Site +patch.inviteEmailTemplates.created=Email templates added: {0} + +patch.htmlNotificationMailTemplates.description=Adds HTML email templates for notifying users of new content + +patch.imapSpacesLocaleTemplates.description=Adds content templates for IMAP clients (Non-default locales only) + +patch.exampleJavaScript.description=Loads sample Javascript file into datadictionary scripts folder \ No newline at end of file diff --git a/config/alfresco/messages/patch-service_es.properties b/config/alfresco/messages/patch-service_es.properties index f8f7075084..bb49df60e8 100755 --- a/config/alfresco/messages/patch-service_es.properties +++ b/config/alfresco/messages/patch-service_es.properties @@ -148,6 +148,11 @@ patch.versionHistoryPerformance.result=Updated {0} version history objects to im patch.multilingualBootstrap.description=Bootstraps the node that will hold the multilingual containers. +patch.siteLoadPatch.description=Loads a sample site into the repository. +patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported +patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration +patch.siteLoadPatch.result=Site {0} imported. + patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} patch.wcmFolders.webprojects.result.created=The Web Projects folder was successfully created: {0} @@ -377,3 +382,21 @@ patch.transfer.targetrule.description=Creates the transfer target rule for the d patch.actions.scheduledfolder.description=Creates the scheduled actions folder in the Data Dictionary. patch.removingLinkValidationMetadata.description=Fixes ALF-5185: Removes all Link Validation reports from schema + +patch.mtFixAdminExistingTenants.description=Fix bootstrapped creator/modifier +patch.mtFixAdminExistingTenants.result=Fix bootstrapped creator/modifier + +patch.fixUserQNames.description=Fixes user store qnames to improve native authentication performance +patch.fixUserQNames.result=Generated qnames for {0} users. + +patch.newUserEmailTemplates.description=Adds the email templates for notifying new users of their accounts +patch.newUserEmailTemplates.created=Email templates added: {0} + +patch.inviteEmailTemplates.description=Adds the email templates for inviting users to a Site +patch.inviteEmailTemplates.created=Email templates added: {0} + +patch.htmlNotificationMailTemplates.description=Adds HTML email templates for notifying users of new content + +patch.imapSpacesLocaleTemplates.description=Adds content templates for IMAP clients (Non-default locales only) + +patch.exampleJavaScript.description=Loads sample Javascript file into datadictionary scripts folder \ No newline at end of file diff --git a/config/alfresco/messages/patch-service_fr.properties b/config/alfresco/messages/patch-service_fr.properties index f8f7075084..bb49df60e8 100755 --- a/config/alfresco/messages/patch-service_fr.properties +++ b/config/alfresco/messages/patch-service_fr.properties @@ -148,6 +148,11 @@ patch.versionHistoryPerformance.result=Updated {0} version history objects to im patch.multilingualBootstrap.description=Bootstraps the node that will hold the multilingual containers. +patch.siteLoadPatch.description=Loads a sample site into the repository. +patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported +patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration +patch.siteLoadPatch.result=Site {0} imported. + patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} patch.wcmFolders.webprojects.result.created=The Web Projects folder was successfully created: {0} @@ -377,3 +382,21 @@ patch.transfer.targetrule.description=Creates the transfer target rule for the d patch.actions.scheduledfolder.description=Creates the scheduled actions folder in the Data Dictionary. patch.removingLinkValidationMetadata.description=Fixes ALF-5185: Removes all Link Validation reports from schema + +patch.mtFixAdminExistingTenants.description=Fix bootstrapped creator/modifier +patch.mtFixAdminExistingTenants.result=Fix bootstrapped creator/modifier + +patch.fixUserQNames.description=Fixes user store qnames to improve native authentication performance +patch.fixUserQNames.result=Generated qnames for {0} users. + +patch.newUserEmailTemplates.description=Adds the email templates for notifying new users of their accounts +patch.newUserEmailTemplates.created=Email templates added: {0} + +patch.inviteEmailTemplates.description=Adds the email templates for inviting users to a Site +patch.inviteEmailTemplates.created=Email templates added: {0} + +patch.htmlNotificationMailTemplates.description=Adds HTML email templates for notifying users of new content + +patch.imapSpacesLocaleTemplates.description=Adds content templates for IMAP clients (Non-default locales only) + +patch.exampleJavaScript.description=Loads sample Javascript file into datadictionary scripts folder \ No newline at end of file diff --git a/config/alfresco/messages/patch-service_it.properties b/config/alfresco/messages/patch-service_it.properties index f8f7075084..bb49df60e8 100755 --- a/config/alfresco/messages/patch-service_it.properties +++ b/config/alfresco/messages/patch-service_it.properties @@ -148,6 +148,11 @@ patch.versionHistoryPerformance.result=Updated {0} version history objects to im patch.multilingualBootstrap.description=Bootstraps the node that will hold the multilingual containers. +patch.siteLoadPatch.description=Loads a sample site into the repository. +patch.siteLoadPatch.exists=The Site {0} already exists and so could not be imported +patch.siteLoadPatch.noBootstrapViews=No bootstrap views were given for importing Site {0} - please check the bootstrap extension bean configuration +patch.siteLoadPatch.result=Site {0} imported. + patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} patch.wcmFolders.webprojects.result.created=The Web Projects folder was successfully created: {0} @@ -377,3 +382,21 @@ patch.transfer.targetrule.description=Creates the transfer target rule for the d patch.actions.scheduledfolder.description=Creates the scheduled actions folder in the Data Dictionary. patch.removingLinkValidationMetadata.description=Fixes ALF-5185: Removes all Link Validation reports from schema + +patch.mtFixAdminExistingTenants.description=Fix bootstrapped creator/modifier +patch.mtFixAdminExistingTenants.result=Fix bootstrapped creator/modifier + +patch.fixUserQNames.description=Fixes user store qnames to improve native authentication performance +patch.fixUserQNames.result=Generated qnames for {0} users. + +patch.newUserEmailTemplates.description=Adds the email templates for notifying new users of their accounts +patch.newUserEmailTemplates.created=Email templates added: {0} + +patch.inviteEmailTemplates.description=Adds the email templates for inviting users to a Site +patch.inviteEmailTemplates.created=Email templates added: {0} + +patch.htmlNotificationMailTemplates.description=Adds HTML email templates for notifying users of new content + +patch.imapSpacesLocaleTemplates.description=Adds content templates for IMAP clients (Non-default locales only) + +patch.exampleJavaScript.description=Loads sample Javascript file into datadictionary scripts folder \ No newline at end of file diff --git a/config/alfresco/messages/patch-service_ja.properties b/config/alfresco/messages/patch-service_ja.properties new file mode 100755 index 0000000000..6616e74850 --- /dev/null +++ b/config/alfresco/messages/patch-service_ja.properties @@ -0,0 +1,385 @@ +# PatchService messages +patch.service.preceeded_by_alternative=Preceded by alternative patch ''{0}''. +patch.service.not_relevant=Not relevant to schema {0} +patch.executer.checking=Checking for patches to apply ... +patch.service.applying_patch=\tApplying patch ''{0}'' ({1}). +patch.progress=\t\tPatch {0} {1}% complete, estimated complete at {2}. +patch.validation.failed=Validation of patch ''{0}'' failed. Patch is applicable to a newer schema than the schema of this build ({1}).\nfixesToSchema: {2}\ntargetSchema: {3}.\nIf this patch should always be run once on every installation, please ensure that the ''fixesToSchema'' value is set to '''${version.schema}'''. +patch.executer.no_patches_required=No patches were required. +patch.executer.system_readonly=Patches cannot be applied to a read-only system. Possible incompatibilities may exist between the application code and the existing data. +patch.executer.not_executed =\n=== Recorded patch (not executed) === \nID: {0}\nRESULT: \n{1}\n===================================== +patch.executer.executed =\n=== Applied patch === \nID: {0}\nRESULT: \n{1}\n===================================== +patch.executer.failed =\n=== Failed to apply patch === \nID: {0}\nRESULT: \n{1}\n===================================== +patch.noLongerSupportedPatch.err.use_incremental_upgrade = \nPatch ''{0}'' was last supported on version {1}.\n Please follow an incremental upgrade using version {2}. + +# General patch messages + +patch.genericBootstrap.result.exists=Bootstrap location already exists: {0} +patch.genericBootstrap.result.created=Imported view into bootstrap location: {0} ({1}) +patch.genericBootstrap.err.multiple_found=Multiple nodes found: {0} + +patch.general.property_not_set=Patch property ''{0}'' has not been set on this patch: {1} + +patch.QNamePatch.result=Successfully updated the ''{0}'' QName to ''{1}''. + +patch.genericMimetypeUpdate.start=Updating mimetypes. +patch.genericMimetypeUpdate.updated=\n\tUpdated {0} content references with mimetype ''{1}'' to use ''{2}''. +patch.genericMimetypeUpdate.indexed=\n\tIndexed {0} nodes in store ''{1}''. +patch.genericMimetypeUpdate.done=\n\tFinished updating mimetypes. +patch.genericMimetypeUpdate.doneReindex=\n\tFinished updating mimetypes. Selective reindexing was disabled; a reindex is required to search for the new mimetype(s). + +# Individual patch messages + +patch.noOpPatch.description=A placeholder patch; usually marks a superceded patch. +patch.noOpPatch.result=No-op patch + +patch.marker.description=Marker patch to record installations and upgrades +patch.marker.result=Marker patch applied + +patch.savedSearchesFolder.description=Ensures the existence of the 'Saved Searches' folder. +patch.savedSearchesFolder.result.exists=The saved searches folder already exists: {0} +patch.savedSearchesFolder.result.created=The saved searches folder was successfully created: {0} + +patch.savedSearchesPermission.description=Sets required permissions on 'Saved Searches' folder. +patch.savedSearchesPermission.result.applied=Granted CONTRIBUTOR role to EVERYONE on 'Saved Searches' folder: {0}. +patch.savedSearchesPermission.err.not_found='Saved Searches' folder could not be found. + +patch.updatePermissionData.description=Update permissions from 'folder' to 'cmobject' [JIRA: AR-344]. +patch.updatePermissionData.result=Changed {0} 'folder' access control entries to 'cmobject'. + +patch.authoritiesFolder.description=Ensures the existence of the user authorities folder [JIRA: AR-497]. + +patch.authoritiesFolderPermission.description=Ensures group authorities are visible to everyone. + +patch.guestUser.description=Add the guest user, guest home space; and fix permissions on company home, guest home and guest person. +patch.guestUser.result=Added guest user and fixed permissions. + +patch.fixNodeSerializableValues.description=Ensure that property values are not stored as Serializable if at all possible +patch.fixNodeSerializableValues.result=Fixed {0} node property serialized values + +patch.updateGuestPermission.description=Rename guest permission from 'Guest' to 'Consumer' +patch.updateGuestPermission.result=Changed {0} 'Guest' access control entries to 'Consumer'. + +patch.categoryRootPermission.description=Sets required permissions on 'Category Root' folder. +patch.categoryRootPermission.result=Granted CONSUMER role to GUEST on 'Category Root' folder: {0}. +patch.categoryRootPermission.err.not_found='Category Root' folder ({0}) could not be found. + +patch.guestPersonPermission.description=Change Guest Person permission from 'Consumer' to 'Read' +patch.guestPersonPermission.result=Updated Guest Person permission from 'Consumer' to 'Read' + +patch.spacesRootPermission.description=Change Spaces store root permission from 'Consumer' to 'Read' +patch.spacesRootPermission.result=Updated Spaces store root permission from 'Consumer' to 'Read' + +patch.contentPermission.description=Update permission entries from 'cm:content' to 'sys:base'. +patch.contentPermission.result=Changed {0} 'cm:content' access control entries to 'sys:base'. + +patch.forumsIcons.description=Updates forums icon references +patch.forumsIcons.result=Updated {0} icon references + +patch.emailTemplatesFolder.description=Ensures the existence of the 'Email Templates' folder. +patch.emailTemplatesFolder.result.exists=The email templates folder already exists: {0} +patch.emailTemplatesFolder.result.created=The email templates folder was successfully created: {0} + +patch.emailInviteAndNotifyTemplatesFolder.description=Ensures the existence of the 'Email Invite Templates' and 'Email Notify Templates' folders. +patch.emailNotifyTemplatesFolder.result.exists=The Email Notify Templates folder already exists: {0} +patch.emailNotifyTemplatesFolder.result.created=The Email Notify Templates folder was successfully created: {0} +patch.emailInviteTemplatesFolder.result.exists=The Email Invite Templates folder already exists: {0} +patch.emailInviteTemplatesFolder.result.created=The Email Invite Templates folder was successfully created: {0} + +patch.emailTemplatesContent.description=Loads the email templates into the Email Templates folder. +patch.emailTemplatesContent.result=Imported the Email Templates into the default folder. + +patch.descriptorUpdate.description=Update Repository descriptor +patch.descriptorUpdate.result=Repository descriptor updated + +patch.scriptsFolder.description=Ensures the existence of the 'Scripts' folder. +patch.scriptsFolder.result.exists=The scripts folder already exists: {0} +patch.scriptsFolder.result.created=The scripts folder was successfully created: {0} + +patch.topLevelGroupParentChildAssociationTypePatch.description=Ensure top level groups have the correct child association type. +patch.topLevelGroupParentChildAssociationTypePatch.result=Fixed {0} top level groups child association types. +patch.topLevelGroupParentChildAssociationTypePatch.err.sys_path_not_found=Required authority system path not found: {0} +patch.topLevelGroupParentChildAssociationTypePatch.err.auth_path_not_found=Required authority path not found: {0} + +patch.actionRuleDecouplingPatch.description=Migrate existing rules to the updated model where rules are decoupled from actions. +patch.actionRuleDecouplingPatch.result=Updated {0} rules. + +patch.systemWorkflowFolder.description=Ensures the existence of the system workflow container. +patch.systemWorkflowFolder.result.created=Created system workflow container {0}. + +patch.rssTemplatesFolder.description=Ensures the existence of the 'RSS Templates' folder. +patch.rssTemplatesFolder.result.exists=The RSS Templates folder already exists: {0}. Re-applying guest permissions. +patch.rssTemplatesFolder.result.created=The RSS Templates folder was successfully created: {0} + +patch.rendition.rendering_actions.exists=The Rendering Actions folder already exists: {0}. +patch.rendition.rendering_actions.created=The Rendering Actions folder was successfully created: {0} +patch.rendition.rendering_actions.description=Creates the Rendering Actions folder. + +patch.replication.replication_actions.exists=The Replication Actions folder already exists: {0}. +patch.replication.replication_actions.created=The Replication Actions folder was successfully created: {0} +patch.replication.replication_actions.description=Creates the Replication Actions folder. + +patch.uifacetsAspectRemovalPatch.description=Removes the incorrectly applied uifacets aspect from presentation template files. +patch.uifacetsAspectRemovalPatch.updated=Successfully removed the uifacets aspect from {0} presentation template files. + +patch.guestPersonPermission2.description=Change Guest Person permission to visible by all users as 'Consumer'. +patch.guestPersonPermission2.result=Updated Guest Person permission to visible by all users as 'Consumer'. + +patch.schemaUpgradeScript.description=Ensures that the database upgrade script has been run. +patch.schemaUpgradeScript.err.not_executed=The schema upgrade script, ''{0}'', has not been run against this database. + +patch.uniqueChildName.description=Checks and renames duplicate children. +patch.uniqueChildName.copyOf=({0}-{1}) +patch.uniqueChildName.result=Checked {0} associations and fixed {1} duplicates. See file {2} for details. +patch.uniqueChildName.err.unable_to_fix=Auto-fixing of duplicate names failed. See file {0} for details. + +patch.invalidNameEnding.description=Fixes names ending with a space or full stop. +patch.invalidNameEnding.result=Fixed {0} names ending with a space or full stop. See file {1} for details. +patch.invalidNameEnding.err.unable_to_fix=Auto-fixing of names ending with a space or full stop failed. See file {0} for details. +patch.invalidNameEnding.rewritten=Name ''{0}'' rewritten to ''{1}'' + +patch.systemDescriptorContent.description=Adds the version properties content to the system descriptor. +patch.systemDescriptorContent.result=Added the version properties content to the system descriptor. +patch.systemDescriptorContent.err.no_version_properties=The version.properties resource could not be found. +patch.systemDescriptorContent.err.no_descriptor=The system descriptor could not be found. + +patch.versionHistoryPerformance.description=Improves the performance of version history lookups. +patch.versionHistoryPerformance.result=Updated {0} version history objects to improve performance. + +patch.multilingualBootstrap.description=Bootstraps the node that will hold the multilingual containers. + +patch.wcmFolders.description=Ensures the existance of the WCM specific 'Web Projects' and 'Web Forms' folders. +patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exists: {0} +patch.wcmFolders.webprojects.result.created=The Web Projects folder was successfully created: {0} +patch.wcmFolders.webforms.result.exists=The Web Forms folder already exists: {0} +patch.wcmFolders.webforms.result.created=The Web Forms folder was successfully created: {0} + +patch.linkNodeExtension.description=Fixes link node file extensions to have a .url extension. +patch.linkNodeExtension.result=Fixed {0} link node file extensions. See file {1} for details. +patch.linkNodeExtension.err.unable_to_fix=Auto-fixing of link node file extensions failed. See file {0} for details. +patch.linkNodeExtension.rewritten=Name ''{0}'' rewritten to ''{1}'' + +patch.systemRegistryBootstrap.description=Bootstraps the node that will hold system registry metadata. + +patch.userAndPersonUserNamesAsIdentifiers.description=Reindex usr:user and cm:person uids as identifiers +patch.userAndPersonUserNamesAsIdentifiers.result=Reindexed user:user and cm:person uids as identifiers + +patch.contentFormFolderType.description=Update WCM Content Form folder type. +patch.contentFormFolderType.result=Updated {0} WCM Content Form objects to 'wcm:formfolder' type. + +patch.groupNamesAsIdentifiers.description=Reindex usr:authorityContainer gids as identifiers +patch.groupNamesAsIdentifiers.result=Reindexed usr:authorityContainer with identifiers + +patch.invalidUserPersonAndGroup.description=Fix up invalid uids for people and users; and invalid gids for groups +patch.invalidUserPersonAndGroup.result=Fixed ''{0}'' invalid user nodes, ''{1}'' invalid person nodes and ''{2}'' invalid authority nodes. + +patch.AVMGuidPatch.description=Set GUIDs on AVM nodes. +patch.AVMGuidPatch.result=AVM GUIDS set. + +patch.webscripts.description=Adds Web Scripts to Data Dictionary. +patch.webscripts2.description=Adds Web Scripts (second set) to Data Dictionary. +patch.webscripts3.description=Update Web Scripts ReadMe. +patch.webscriptsExtension.description=Adds Web Scripts Extension to Data Dictionary. + +patch.AVMLayeredSnapshot.description=Set indirectionVersion on Layered Nodes. +patch.AVMLayeredSnapshot.result=Layered Node indirectionVersions set. + +patch.groupMembersAsIdentifiers.description=Reindex usr:authorityContainer members as identifiers + +patch.genericWorkflow.result.deployed=Re-deployed {0} workflows. + +patch.redeploySubmitProcess.description=Re-deploy WCM Submit Process Definition. +patch.deploySubmitDirectProcess.description=Deploy WCM Direct Submit Process Definition. + +patch.AVMAspects.description=Changes storage of aspects on AVM Nodes. +patch.AVMAspects.result=Aspects were moved. + +patch.ReadmeTemplate.description=Deployed ReadMe Template +patch.webScriptsReadme.description=Applied ReadMe template to Web Scripts folders + +patch.AVMProperties.description=Changes storage of properties on AVM Nodes. +patch.AVMProperties.result=Properties were moved. + +patch.customModels.description=Adds 'Models' folder to Data Dictionary +patch.customMessages.description=Adds 'Messages' folder to Data Dictionary +patch.customWebClientExtension.description=Adds 'Web Client Extension' folder to Data Dictionary + +patch.customWorkflowDefs.description=Adds 'Workflow Definitions' folder to Data Dictionary. + +patch.emailContributorGroup.description=Adds the 'GROUP_EMAIL_CONTRIBUTORS' group. + +patch.avmStoreAsIdentifier.description=Reindex wca:webfolder to make wca:avmstore an identifier +patch.avmStoreAsIdentifier.result=Reindexed wca:webfolder to make wca:avmstore an identifier + +patch.avmFormPropertyIdentifier.description=Reindex wca:webform to make wca:formname an identifier +patch.avmFormPropertyIdentifier.result=Reindexed wca:webform to make wca:formname an identifier + +patch.formsFolder.description=Adds 'Forms' folder to Data Dictionary. + +patch.tagRootCategory.description=Adds 'Tags' as new top-level category root. + +patch.sitesFolder.description=Adds 'Sites' folder to Company Home. + +patch.deploymentMigration.description=Migrates deployment data to the new model. +patch.deploymentMigration.webProjectName=Migrating deployment data for web project ''{0}'' +patch.deploymentMigration.serverMigrated=Server ''{0}'' from web project ''{1}'' has been migrated +patch.deploymentMigration.reportMigrated=Deployment report for ''{0}'' from web project ''{1}'' has been migrated +patch.deploymentMigration.result=Deployment data has been migrated. + +patch.updateAvmPermissionData.description=Update avm permissions from 'webfolder' to 'cmobject'. +patch.updateAvmPermissionData.result=Changed {0} 'webfolder' access control entries to 'cmobject'. + +patch.updateAvmPermissions.description=Update ACLs on all avm objects to the new 2.2 permission model +patch.updateAvmPermissions.result=Updated ACLs. Created {0} defining and {1} layered ACLs. + +patch.wcmPermissionPatch.description=Fix ACLs so they are only set on the staging area store. +patch.wcmPermissionPatch.result=Updated ACLs: ACLS are moved to the staging area store and removed from all other stores. They are now applied via layering. + +patch.avmWebProjectInheritPermissions.description=Break inheritance of permissions on wca:webfolder object to hide access by default. +patch.avmWebProjectInheritPermissions.result=Removed inheritance of permissions on all wca:webfolder objects. + +patch.wcmPostPermissionSnapshotPatch.description=Snapshot stores (after fixing ACLs so they are only set on the staging area store). +patch.wcmPostPermissionSnapshotPatch.result=Snapshot complete after WCM ACL changes. + +patch.updateDmPermissions.description=Update ACLs on all DM node objects to the new 3.0 permission model +patch.updateDmPermissions.result=Updated ACLs. Created {0} defining ACLs. + +patch.db-V3.0-0-CreateActivitiesExtras.description=Replaced by 'patch.db-V3.0-ActivityTables', which must run first. + +patch.createSiteStore.description=Create the AVM store for site data structure for 3.0 web-tier clients. +patch.createSiteStore.result=Created the AVM site data store. + +patch.sitePermissionRefactorPatch.description=Create permission groups for sites. +patch.sitePermissionRefactorPatch.result=Groups have been created for all sites and user's allocated accordingly. + +patch.migrateVersionStore.description=Version Store migration (from lightWeightVersionStore to version2Store) +patch.migrateVersionStore.incomplete=Version Store migration incomplete. +patch.migrateVersionStore.done=Version Store migration completed. +patch.migrateVersionStore.bypassingPatch=Bypass Version Store migration patch since scheduled to run as job + +patch.inviteEmailTemplate.description=Adds invite email template to invite space + +patch.calendarModelNamespacePatch.description=Update the Calendar model namespace URI and reindex all calendar objects. +patch.calendarModelNamespacePatch.result=Updated the Calendar model namespace URI to http://www.alfresco.org/model/calendar and reindexed {0} calendar objects. + +patch.spacesStoreGuestPermission.description=Sets READ permissions for GUEST on root node of the SpacesStore. +patch.spacesStoreGuestPermission.result=Granted READ permissions for GUEST on root node of the SpacesStore. + +patch.administratorGroup.description=Adds the 'ALFRESCO_ADMINISTRATORS' group. + +patch.resetWCMToGroupBasedPermissionsPatch.description=Reset WCM to group based permissions. +patch.resetWCMToGroupBasedPermissionsPatch.result=WCM reset to group based permissions. + +patch.migrateVersionStoreUpdateCounter.description=Update internal version2Store counter (if needed). +patch.migrateVersionStoreUpdateCounter.result=Update internal version2Store counter (if needed): {0} + +patch.invitationMigration.description=Migrate invitations from old invite service to invitation service +patch.invitationMigration.result=Migrated {0} invitations from old invite service to invitation service. +patch.invitationMigration.no_invites=No invitations require migrating old invite service to invitation service. + +patch.webSiteAddModerated.description=Changing Web Site visibility from a boolean to enum. +patch.webSiteAddModerated.result=Changed Web Site visibility. + +patch.mtShareExistingTenants.description=Update existing tenants for MT Share. +patch.mtShareExistingTenants.result=Update existing tenants for MT Share. +patch.mtShareExistingTenants.result.not_applicable=Patch applied, although no changes made since MT is not enabled. + +patch.redeployInvitationProcess.description=Re-deploy Invitation Process Definitions. + +patch.imapFolders.description=Creates folders tree necessary for IMAP functionality +patch.imapFolders.result.exists=The 'Imap Configs' folder already exists +patch.imapFolders.result.created=The 'Imap Configs' folder was successfully created + +patch.zonedAuthorities.description=Adds the remodelled cm:authority container to the spaces store + +patch.authorityMigration.description=Copies any old authorities from the user store to the spaces store. +patch.authorityMigration.process.name=Authority Migration +patch.authorityMigration.warning.assoc=Ignoring group memberships of non-existent user {1} +patch.authorityMigration.result=Migrated {0} groups and {1} group associations to the spaces store. + +patch.authorityDefaultZonesPatch.description=Adds groups and people to the appropriate zones for wcm, share and everything else. +patch.authorityDefaultZonesPatch.result=Unzoned groups and people added to the default zones. +patch.authorityDefaultZonesPatch.users= Adding users to zones ... +patch.authorityDefaultZonesPatch.groups= Adding groups to zones ... + +patch.fixNameCrcValues.description=Fixes name and qname CRC32 values to match UTF-8 encoding. +patch.fixNameCrcValues.result=Fixed CRC32 values for UTF-8 encoding for {0} node child associations. See file {1} for details. +patch.fixNameCrcValues.fixed=Updated CRC32 values for association ID {0}, name ''{1}'': {2} -> {3}, qname ''{4}'': {5} -> {6}. +patch.fixNameCrcValues.unableToChange=Failed to update the CRC32 value for association ID {0}: \n Node name: {1} \n name CRC old: {2} \n name CRC new: {3} \n qname: {4} \n qname CRC old: {5} \n qname CRC new: {6} \n Error: {7} +patch.fixNameCrcValues.fixingLocalname=Fixing invalid localname for association ID {0}: \n Was: ''{1}'' \n Now: ''{2}'' +patch.fixNameCrcValues.associationTypeNotDefined=Association type ''{0}'' has not been defined for child association ID {1}. +patch.fixNameCrcValues.associationTypeNotChild=Association type ''{0}'' does not represent a child association but is used as one; for child association ID {1}. + +patch.personUsagePatch.description=Add person 'cm:sizeCurrent' property (if missing). +patch.personUsagePatch.result1=Added 'cm:sizeCurrent' property to {0} people that were missing this property. +patch.personUsagePatch.result2=No people were missing the 'cm:sizeCurrent' property. + +patch.redeployNominatedInvitationProcessWithPropsForShare.description=Redeploy nominated invitation workflow +patch.redeployNominatedInvitationProcessWithPropsForShare.result=Nominated invitation workflow redeployed + +patch.transferDefinitions.description=Add transfer definitions folder to data dictionary. +patch.transferDefinitions.result=Transfer definitions folder added to data dictionary. + +patch.redeployNominatedInvitationProcessWithPropsForShare.description=Redeploy nominated invitation workflow +patch.redeployNominatedInvitationProcessWithPropsForShare.result=Nominated invitation workflow redeployed + +patch.thumbnailsAssocQName.description=Update the 'cm:thumbnails' association QName to 'rn:rendition'. + +patch.convertContentUrls.description=Converts pre-3.2 content URLs to use the alf_content_data table. The conversion work can also be done on a schedule; please contact Alfresco Support for further details. +patch.convertContentUrls.bypassingPatch=Content URL conversion was NOT performed by this patch. Activate the scheduled job 'contentUrlConverterTrigger'. +patch.convertContentUrls.start=Content URL conversion progress: +patch.convertContentUrls.error=Content URL conversion failed: {0} +patch.convertContentUrls.inProgress=Content URL conversion increment completed. Awaiting next scheduled call... +patch.convertContentUrls.done=Content URL conversion completed. +patch.convertContentUrls.adm.start=\tProcessing ADM Content URLs. +patch.convertContentUrls.adm.done=\tFinished processing ADM nodes up to ID {0}. +patch.convertContentUrls.avm.start=\tProcessing AVM Content URLs. +patch.convertContentUrls.avm.done=\tFinished processing AVM nodes up to ID {0}. +patch.convertContentUrls.store.start=\tReading content URLs from store {0}. +patch.convertContentUrls.store.readOnly=\tNo content URLs will be marked for deletion. The content store is read-only. +patch.convertContentUrls.store.pending=\tContent URLs will be marked for deletion once the URL conversion process is complete. +patch.convertContentUrls.store.noSupport=\tNo content URLs will be marked for deletion. The store does not support URL enumeration. +patch.convertContentUrls.store.progress=\t\tProcessed {0} content URLs from store. +patch.convertContentUrls.store.scheduled=\tScheduled {0} content URLs for deletion from store: {1} +patch.convertContentUrls.store.done=This job is complete. Deactivate the scheduled job 'contentUrlConverterTrigger'. + +patch.fixAuthoritiesCrcValues.description=Fixes authority CRC32 values to match UTF-8 encoding. +patch.fixAuthoritiesCrcValues.result=Fixed CRC32 values for UTF-8 encoding for {0} authorities. See file {1} for details. +patch.fixAuthoritiesCrcValues.fixed=Updated CRC32 values for authority '{0}'. +patch.fixAuthoritiesCrcValues.unableToChange=Failed to update the CRC32 value for authority: \n Authority: {0} \n Error: {1} + +patch.updateMimetypes1.description=Fix mimetypes for Excel and Powerpoint. +patch.updateMimetypes2.description=Fix mimetypes for Excel and Powerpoint. + +patch.db-V3.2-AddFKIndexes.description=Fixes ALF-3189: Added missing FK indexes. Note: The script is empty for MySQL. + +patch.eliminateDuplicates.description=Fixes ALF-4203: Searches for AVM duplicate nodes and changes their name + +patch.migrateAttrTenants.description=Migrate old Tenant attributes +patch.migrateAttrTenants.result=Processed {0} attributes + +patch.migrateAttrAVMLocks.description=Migrate old AVM Lock attributes +patch.migrateAttrAVMLocks.result=Processed {0} attributes + +patch.migrateAttrPropBackedBeans.description=Migrate old Property-Backed Bean component attributes +patch.migrateAttrPropBackedBeans.result=Processed {0} attributes ({1} properties) + +patch.migrateAttrChainingURS.description=Migrate old Chaining User Registry Synchronizer attributes +patch.migrateAttrChainingURS.result=Processed {0} attributes + +patch.migrateAttrDelete.description=Delete old attributes (if any) after they have been migrated +patch.migrateAttrDelete.result=Old attributes were deleted (if any) + +patch.transfer.targetrulefolder.description=Creates the transfer target rule folder for the default transfer group. + +patch.transfer.targetrule.description=Creates the transfer target rule for the default transfer group. + +patch.actions.scheduledfolder.description=Creates the scheduled actions folder in the Data Dictionary. + +patch.removingLinkValidationMetadata.description=Fixes ALF-5185: Removes all Link Validation reports from schema + +patch.mtFixAdminExistingTenants.description=Fix bootstrapped creator/modifier +patch.mtFixAdminExistingTenants.result=Fix bootstrapped creator/modifier + +patch.fixUserQNames.description=Fixes user store qnames to improve native authentication performance +patch.fixUserQNames.result=Generated qnames for {0} users. diff --git a/config/alfresco/messages/period-provider_ja.properties b/config/alfresco/messages/period-provider_ja.properties new file mode 100755 index 0000000000..56e4592c22 --- /dev/null +++ b/config/alfresco/messages/period-provider_ja.properties @@ -0,0 +1,17 @@ +# PeriodProvider display label strings + +period_provider.cron=Cron\u5f0f +period_provider.day=\u65e5 +period_provider.fmend=\u4f1a\u8a08\u6708\u672b +period_provider.fqend=\u56db\u534a\u671f\uff08\u4f1a\u8a08\u671f\uff09\u672b +period_provider.fyend=\u4f1a\u8a08\u5e74\u672b +period_provider.immediately=\u5373\u6642 +period_provider.monthend=\u6708\u672b +period_provider.quarterend=\u56db\u534a\u671f\u672b +period_provider.yearend=\u5e74\u672b +period_provider.month=\u6708 +period_provider.none=\u306a\u3057 +period_provider.quarter=\u56db\u534a\u671f +period_provider.week=\u9031 +period_provider.duration=XML\u6301\u7d9a\u671f\u9593 +period_provider.year=\u5e74 diff --git a/config/alfresco/messages/permissions-service_it.properties b/config/alfresco/messages/permissions-service_it.properties index da3498ddaa..5e25881235 100755 --- a/config/alfresco/messages/permissions-service_it.properties +++ b/config/alfresco/messages/permissions-service_it.properties @@ -1,2 +1,2 @@ -permissions.err_access_denied=Accesso negato. Non si dispone di permessi appropriati per eseguire questa operazione. -permissions.err_read_only=Accesso negato. Il sistema \u00e8 attualmente in modalit\u00e0 di sola lettura. +permissions.err_access_denied=Accesso Negato. Non si dispone di permessi appropriati per eseguire questa operazione. +permissions.err_read_only=Accesso Negato. Il sistema \u00e8 attualmente in modalit\u00e0 di sola lettura. diff --git a/config/alfresco/messages/permissions-service_ja.properties b/config/alfresco/messages/permissions-service_ja.properties new file mode 100755 index 0000000000..1c49f9f33f --- /dev/null +++ b/config/alfresco/messages/permissions-service_ja.properties @@ -0,0 +1,2 @@ +permissions.err_access_denied=\u30a2\u30af\u30bb\u30b9\u304c\u62d2\u5426\u3055\u308c\u307e\u3057\u305f\u3002 \u3053\u306e\u30aa\u30da\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u305f\u3081\u306e\u9069\u5207\u306a\u6a29\u9650\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +permissions.err_read_only=\u30a2\u30af\u30bb\u30b9\u304c\u62d2\u5426\u3055\u308c\u307e\u3057\u305f\u3002 \u30b7\u30b9\u30c6\u30e0\u306f\u73fe\u5728\u8aad\u53d6\u5c02\u7528\u30e2\u30fc\u30c9\u3067\u3059\u3002 diff --git a/config/alfresco/messages/rendition-config_ja.properties b/config/alfresco/messages/rendition-config_ja.properties new file mode 100755 index 0000000000..f09909f74f --- /dev/null +++ b/config/alfresco/messages/rendition-config_ja.properties @@ -0,0 +1,45 @@ +# Rendering Engines i18n properties + +# The following are common to all rendering engines +baseRenderingAction.runAs.display-label=\u5b9f\u884c +baseRenderingAction.update-renditions-on-any-property-change.display-label=\u30d7\u30ed\u30d1\u30c6\u30a3\u5909\u66f4\u6642\u306b\u63cf\u753b\u3092\u66f4\u65b0 +baseRenderingAction.rendition-nodetype.display-label=\u63cf\u753b\u30ce\u30fc\u30c9\u30bf\u30a4\u30d7 +baseRenderingAction.placeHolderResourcePath.display-label=\u30d7\u30ec\u30fc\u30b9\u30db\u30eb\u30c0\u306e\u30ea\u30bd\u30fc\u30b9\u30d1\u30b9 +baseRenderingAction.sourceContentProperty.display-label=\u30bd\u30fc\u30b9\u30b3\u30f3\u30c6\u30f3\u30c4\u30d7\u30ed\u30d1\u30c6\u30a3 +baseRenderingAction.targetContentProperty.display-label=\u30bf\u30fc\u30b2\u30c3\u30c8\u30b3\u30f3\u30c6\u30f3\u30c4\u30d7\u30ed\u30d1\u30c6\u30a3 +baseRenderingAction.destination-path-template.display-label=\u5b9b\u5148\u30d1\u30b9\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 +baseRenderingAction.orphan-existing-rendition.display-label=\u5b64\u7acb\u884c\u306e\u65e2\u5b58\u306e\u63cf\u753b + +# The following are common to all template-based rendering engines +baseTemplateRenderingAction.model.display-label=\u30e2\u30c7\u30eb +baseTemplateRenderingAction.template_string.display-label=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u6587\u5b57\u5217 +baseTemplateRenderingAction.template_node.display-label=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30ce\u30fc\u30c9 +baseTemplateRenderingAction.template_path.display-label=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30d1\u30b9 + +# reformat +reformat.title=\u63cf\u753b\u518d\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u306e\u5b9f\u884c +reformat.description=\u4ed6\u306e\u5f62\u5f0f(MIME\u30bf\u30a4\u30d7)\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u90e8\u5206\u3092\u30ec\u30f3\u30c0\u30ea\u30f3\u30b0\u3057\u307e\u3059\u3002 +reformat.flashVersion.display-label=Flash\u30d0\u30fc\u30b8\u30e7\u30f3 +reformat.mime-type.display-label=MIME\u30bf\u30a4\u30d7 + +# imageRenderingEngine +imageRenderingEngine.title=\u753b\u50cf\u51e6\u7406\u63cf\u753b\u306e\u5b9f\u884c +imageRenderingEngine.xsize.display-label=\u5e45 +imageRenderingEngine.ysize.display-label=\u9ad8\u3055 +imageRenderingEngine.isAbsolute.display-label=\u7d76\u5bfe\u6307\u5b9a +imageRenderingEngine.maintainAspectRatio.display-label=\u30a2\u30b9\u30da\u30af\u30c8\u6bd4\u306e\u4fdd\u6301 +imageRenderingEngine.resizeToThumbnail.display-label=\u30b5\u30e0\u30cd\u30a4\u30eb\u306b\u30ea\u30b5\u30a4\u30ba +imageRenderingEngine.crop_gravity.display-label=\u91cd\u529b +imageRenderingEngine.crop_height.display-label=\u30c8\u30ea\u30df\u30f3\u30b0\u9ad8\u3055 +imageRenderingEngine.crop_width.display-label=\u30c8\u30ea\u30df\u30f3\u30b0\u5e45 +imageRenderingEngine.crop_x.display-label=\u30c8\u30ea\u30df\u30f3\u30b0 x +imageRenderingEngine.crop_y.display-label=\u30c8\u30ea\u30df\u30f3\u30b0 y +imageRenderingEngine.percent_crop.display-label=\u30d1\u30fc\u30bb\u30f3\u30c8\u30c8\u30ea\u30df\u30f3\u30b0 +imageRenderingEngine.commandOptions.display-label=\u30b3\u30de\u30f3\u30c9\u30aa\u30d7\u30b7\u30e7\u30f3 + +# freemarkerRenderingEngine +freemarkerRenderingEngine.title=Freemarker\u30d9\u30fc\u30b9\u63cf\u753b\u306e\u5b9f\u884c +freemarkerRenderingEngine.image_resolver.display-label=\u753b\u50cf\u30ea\u30be\u30eb\u30d0 + +# xsltRenderingEngine +xsltRenderingEngine.title=XSLT\u30d9\u30fc\u30b9\u63cf\u753b\u306e\u5b9f\u884c diff --git a/config/alfresco/messages/repoadmin-interpreter-help_ja.properties b/config/alfresco/messages/repoadmin-interpreter-help_ja.properties new file mode 100755 index 0000000000..c5eb0c985f --- /dev/null +++ b/config/alfresco/messages/repoadmin-interpreter-help_ja.properties @@ -0,0 +1 @@ +repoadmin_console.help=alfresco/messages/repoadmin-interpreter-help.txt diff --git a/config/alfresco/messages/rule-config_ja.properties b/config/alfresco/messages/rule-config_ja.properties new file mode 100755 index 0000000000..3e7b3910d5 --- /dev/null +++ b/config/alfresco/messages/rule-config_ja.properties @@ -0,0 +1,6 @@ +# Rule types + +inbound.display-label=\u30a2\u30a4\u30c6\u30e0\u306f\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u3067\u4f5c\u6210\u307e\u305f\u306f\u5165\u529b\u3055\u308c\u307e\u3059\u3002 +outbound.display-label=\u30a2\u30a4\u30c6\u30e0\u306f\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u304b\u3089\u524a\u9664\u307e\u305f\u306f\u6b8b\u3055\u308c\u307e\u3059\u3002 +update.display-label=\u30a2\u30a4\u30c6\u30e0\u304c\u66f4\u65b0\u3055\u308c\u307e\u3059\u3002 +inboundAndUpdate.display-label=\u30a2\u30a4\u30c6\u30e0\u304c\u4f5c\u6210\u3055\u308c\u3001\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u306b\u5165\u308b\u304b\u66f4\u65b0\u3055\u308c\u307e\u3059\u3002 diff --git a/config/alfresco/messages/schema-update_ja.properties b/config/alfresco/messages/schema-update_ja.properties new file mode 100755 index 0000000000..a7afca61c3 --- /dev/null +++ b/config/alfresco/messages/schema-update_ja.properties @@ -0,0 +1,29 @@ +# Schema update messages + +schema.update.msg.dialect_used=Schema managed by database dialect {0}. +schema.update.msg.bypassing=Bypassing schema update checks. +schema.update.msg.normalized_schema=Normalized schema dumped to file {0}. +schema.update.msg.normalized_schema_pre=Normalized schema (pre-bootstrap) dumped to file {0}. +schema.update.msg.normalized_schema_post=Normalized schema (post-bootstrap) dumped to file {0}. +schema.update.msg.all_statements=All executed statements: {0}. +schema.update.msg.no_changes=No changes were made to the schema. +schema.update.msg.executing_generated_script=Executing database script {0} (Generated). +schema.update.msg.executing_copied_script=Executing database script {0} (Copied from {1}). +schema.update.msg.executing_statement= Executing statement: {0} +schema.update.msg.optional_statement_failed=Optional statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3} +schema.update.warn.dialect_unsupported=Alfresco should not be used with database dialect {0}. +schema.update.warn.dialect_hsql=Alfresco is using the HSQL default database. Please only use this while evaluating Alfresco, it is NOT recommended for production or deployment! +schema.update.warn.dialect_derby=Alfresco is using the Apache Derby default database. Please only use this while evaluating Alfresco, it is NOT recommended for production or deployment! +schema.update.warn.dialect_substituting=The dialect ''{0}'' is being changed to ''{1}''. +schema.update.err.forced_stop=The property 'stopAfterSchemaBootstrap' has been set. The bootstrap process is being terminated. +schema.update.err.dialect_should_use=The dialect ''{0}'' is unsupported. Please use ''{1}'' instead. +schema.update.err.found_multiple=\nMore than one Alfresco schema was found when querying the database metadata.\n Limit the database user's permissions or set the 'hibernate.default_schema' property in 'custom-hibernate-dialect.properties'. +schema.update.err.previous_failed=A previous schema upgrade failed or was not completed. Revert to the original database before attempting the upgrade again. +schema.update.err.statement_failed=Statement execution failed:\n SQL: {0}\n Error: {1}\n File: {2}\n Line: {3} +schema.update.err.update_failed=Schema auto-update failed +schema.update.err.validation_failed=Schema validation failed +schema.update.err.update_script_not_run=The following schema upgrade script needs to be executed manually: {0} +schema.update.err.script_not_found=The schema script could not be found at location {0} +schema.update.err.statement_var_assignment_before_sql=Variable assignment with '--ASSIGN:' must occur before starting the SQL statement (line {0} of {1}). +schema.update.err.statement_var_assignment_format=Variable assignment uses format '--ASSIGN:x=col' where 'x' is the variable to assign to and 'col' is the column value to extract (line {0} of {1}). +schema.update.err.statement_terminator=Scripts must terminate all statements with ';' (line {0} of {1}). diff --git a/config/alfresco/messages/site-service.properties b/config/alfresco/messages/site-service.properties index 972ad220c5..d9d66eeabf 100644 --- a/config/alfresco/messages/site-service.properties +++ b/config/alfresco/messages/site-service.properties @@ -6,7 +6,7 @@ site_service.can_not_update=Cannot update site {0} because it does not exist. site_service.can_not_delete=Cannot delete site {0} because it does not exist. site_service.site_no_exist=Site {0} does not exist. site_service.do_not_remove_manager=A site requires at least one site manager. You cannot remove {0} from the site membership because they are currently the only site manager. -site_service.can_not_reomve_memebership=The current user does not have sufficient permissions to delete membership details of the site {0}. +site_service.can_not_remove_membership=The current user does not have sufficient permissions to delete membership details of the site {0}. site_service.do_not_change_manager=A site requires at least one site manager. You cannot change the role of {0}, because they are currently the only site manager. -site_service.can_not_change_memebership=The current user does not have permissions to modify the membership details of the site {0}. +site_service.can_not_change_membership=The current user does not have permissions to modify the membership details of the site {0}. site_service.site_container_not_folder=Site container {0} does not refer to a folder. \ No newline at end of file diff --git a/config/alfresco/messages/site-service_de.properties b/config/alfresco/messages/site-service_de.properties index 6d37b451e5..22f65ca0b5 100755 --- a/config/alfresco/messages/site-service_de.properties +++ b/config/alfresco/messages/site-service_de.properties @@ -6,7 +6,7 @@ site_service.can_not_update=Site {0} kann nicht aktualisiert werden, da sie nich site_service.can_not_delete=Kann Site {0} nicht l\u00f6schen, da sie nicht existiert. site_service.site_no_exist=Site {0} existiert nicht. site_service.do_not_remove_manager=Eine Site ben\u00f6tigt mindestens einen Site Manager. Sie k\u00f6nnen {0} nicht aus der Site-Mitgliedschaft entfernen, da sie derzeit die einzigen Site Manager sind. -site_service.can_not_reomve_memebership=Der aktuelle Benutzer verf\u00fcgt nicht \u00fcber ausreichend Berechtigungen, um Mitgliedschaftsdetails der Site {0} zu l\u00f6schen. +site_service.can_not_remove_membership=Der aktuelle Benutzer verf\u00fcgt nicht \u00fcber ausreichend Berechtigungen, um Mitgliedschaftsdetails der Site {0} zu l\u00f6schen. site_service.do_not_change_manager=Eine Site ben\u00f6tigt mindestens einen Site Manager. Sie k\u00f6nnen die Rolle von {0} nicht \u00e4ndern, da sie derzeit die einzigen Site Manager sind. -site_service.can_not_change_memebership=Der aktuelle Benutzer verf\u00fcgt nicht \u00fcber die Berechtigungen, um Mitgliedschaftsdetails der Site {0} zu \u00e4ndern. +site_service.can_not_change_membership=Der aktuelle Benutzer verf\u00fcgt nicht \u00fcber die Berechtigungen, um Mitgliedschaftsdetails der Site {0} zu \u00e4ndern. site_service.site_container_not_folder=Site Container {0} bezieht sich nicht auf einen Ordner. diff --git a/config/alfresco/messages/site-service_es.properties b/config/alfresco/messages/site-service_es.properties index f1b3cbb828..3026f3bfe0 100755 --- a/config/alfresco/messages/site-service_es.properties +++ b/config/alfresco/messages/site-service_es.properties @@ -6,7 +6,7 @@ site_service.can_not_update=No se puede actualizar el sitio {0} porque no existe site_service.can_not_delete=No se puede eliminar el sitio {0} porque no existe. site_service.site_no_exist=El sitio {0} no existe. site_service.do_not_remove_manager=Un sitio requiere al menos un administrador del sitio. Usted no puede quitar a {0} de la pertenencia al sitio, ya que actualmente es el \u00fanico administrador del sitio. -site_service.can_not_reomve_memebership=El usuario actual no tiene permisos suficientes para eliminar detalles de miembros del sitio {0}. +site_service.can_not_remove_membership=El usuario actual no tiene permisos suficientes para eliminar detalles de miembros del sitio {0}. site_service.do_not_change_manager=Un sitio requiere al menos un administrador del sitio. Usted no puede cambiar el rol de {0}, ya que actualmente es el \u00fanico administrador del sitio. -site_service.can_not_change_memebership=El usuario actual no tiene permisos para modificar detalles de pertenencia al sitio {0}. -site_service.site_container_not_folder=El contenedor del sitio {0} no se refiere a una carpeta. +site_service.can_not_change_membership=El usuario actual no tiene permisos para modificar detalles de pertenencia al sitio {0}. +site_service.site_container_not_folder=El contenedor del sitio {0} no se refiere a una carpeta. \ No newline at end of file diff --git a/config/alfresco/messages/site-service_fr.properties b/config/alfresco/messages/site-service_fr.properties index a5c96bfd8d..ee981a5631 100755 --- a/config/alfresco/messages/site-service_fr.properties +++ b/config/alfresco/messages/site-service_fr.properties @@ -1,12 +1,12 @@ # Site service externalised display strings site_service.unable_to_create=Impossible de cr\u00e9er le site car le nom court du site {0} est d\u00e9j\u00e0 utilis\u00e9. Les noms courts de site doivent \u00eatre uniques. -site_service.visibility_group_missing=Impossible de cr\u00e9er le site car le groupe de visibilit\u00e9 {0} n'existe pas. -site_service.can_not_update=Impossible de mettre \u00e0 jour le site {0} car celui-ci n'existe pas. -site_service.can_not_delete=Impossible de supprimer le site {0} car celui-ci n'existe pas. +site_service.visibility_group_missing=Impossible de cr\u00e9er le site car le groupe de visibilit\u00e9 {0} n''existe pas. +site_service.can_not_update=Impossible de mettre \u00e0 jour le site {0} car celui-ci n''existe pas. +site_service.can_not_delete=Impossible de supprimer le site {0} car celui-ci n''existe pas. site_service.site_no_exist=Le site {0} n''existe pas. site_service.do_not_remove_manager=Un site n\u00e9cessite au moins un gestionnaire de site. Vous ne pouvez pas retirer \u00e0 {0} l''acc\u00e8s au site car il s''agit du seul gestionnaire du site. -site_service.can_not_reomve_memebership=L''utilisateur actuel ne dispose pas des permissions suffisantes pour supprimer les d\u00e9tails d''appartenance du site {0}. -site_service.do_not_change_manager=Un site n\u00e9cessite au moins un gestionnaire de site. Vous ne pouvez pas modifier le r\u00f4le de 0 car il s'agit du seul gestionnaire du site. -site_service.can_not_change_memebership=L''utilisateur actuel ne dispose pas des permissions suffisantes pour modifier les d\u00e9tails d''appartenance du site {0}. +site_service.can_not_remove_membership=L''utilisateur actuel ne dispose pas des permissions suffisantes pour supprimer les d\u00e9tails d''appartenance du site {0}. +site_service.do_not_change_manager=Un site n\u00e9cessite au moins un gestionnaire de site. Vous ne pouvez pas modifier le r\u00f4le de {0} car il s''agit du seul gestionnaire du site. +site_service.can_not_change_membership=L''utilisateur actuel ne dispose pas des permissions suffisantes pour modifier les d\u00e9tails d''appartenance du site {0}. site_service.site_container_not_folder=Le conteneur de site {0} ne se r\u00e9f\u00e8re pas \u00e0 un dossier. diff --git a/config/alfresco/messages/site-service_it.properties b/config/alfresco/messages/site-service_it.properties index 491f128665..f61d98003e 100755 --- a/config/alfresco/messages/site-service_it.properties +++ b/config/alfresco/messages/site-service_it.properties @@ -5,8 +5,8 @@ site_service.visibility_group_missing=Impossibile creare il sito perch\u00e9 il site_service.can_not_update=Impossibile aggiornare il sito {0} perch\u00e9 non esiste. site_service.can_not_delete=Impossibile eliminare il sito {0} perch\u00e9 non esiste. site_service.site_no_exist=Il sito {0} non esiste. -site_service.do_not_remove_manager=Un sito deve avere almeno un manager. Non \u00e8 possibile rimuovere {0} dalle appartenenze al sito perch\u00e9 attualmente \u00e8 l'unico manager. -site_service.can_not_reomve_memebership=L'utente attuale non dispone di permessi sufficienti per eliminare i dettagli delle appartenenze del sito {0}. -site_service.do_not_change_manager=Un sito deve avere almeno un manager. Non \u00e8 possibile cambiare il ruolo di {0} perch\u00e9 attualmente \u00e8 l'unico manager. -site_service.can_not_change_memebership=L'utente attuale non dispone di permessi per modificare i dettagli delle appartenenze del sito {0}. +site_service.do_not_remove_manager=Un sito deve avere almeno un manager. Non \u00e8 possibile rimuovere {0} dalle appartenenze al sito perch\u00e9 attualmente \u00e8 l''unico manager. +site_service.can_not_remove_membership=L''utente attuale non dispone di permessi sufficienti per eliminare i dettagli delle appartenenze del sito {0}. +site_service.do_not_change_manager=Un sito deve avere almeno un manager. Non \u00e8 possibile cambiare il ruolo di {0} perch\u00e9 attualmente \u00e8 l''unico manager. +site_service.can_not_change_membership=L''utente attuale non dispone di permessi per modificare i dettagli delle appartenenze del sito {0}. site_service.site_container_not_folder=Il contenitore del sito {0} non referenzia una cartella. diff --git a/config/alfresco/messages/site-service_ja.properties b/config/alfresco/messages/site-service_ja.properties new file mode 100755 index 0000000000..1e01c593cb --- /dev/null +++ b/config/alfresco/messages/site-service_ja.properties @@ -0,0 +1,12 @@ +# Site service externalised display strings + +site_service.unable_to_create=\u30b5\u30a4\u30c8\u7701\u7565\u540d {0} \u304c\u3059\u3067\u306b\u4f7f\u7528\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30b5\u30a4\u30c8\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3002 \u30b5\u30a4\u30c8\u7701\u7565\u540d\u306f\u56fa\u6709\u306e\u3082\u306e\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002 +site_service.visibility_group_missing=\u516c\u958b\u30ec\u30d9\u30eb\u30b0\u30eb\u30fc\u30d7 {0} \u304c\u5b58\u5728\u3057\u306a\u3044\u305f\u3081\u3001\u30b5\u30a4\u30c8\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3002 +site_service.can_not_update=\u30b5\u30a4\u30c8 {0} \u304c\u5b58\u5728\u3057\u306a\u3044\u305f\u3081\u3001\u66f4\u65b0\u3067\u304d\u307e\u305b\u3093\u3002 +site_service.can_not_delete=\u30b5\u30a4\u30c8 {0} \u304c\u5b58\u5728\u3057\u306a\u3044\u305f\u3081\u3001\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3002 +site_service.site_no_exist=\u30b5\u30a4\u30c8 {0} \u304c\u5b58\u5728\u3057\u307e\u305b\u3093\u3002 +site_service.do_not_remove_manager=\u30b5\u30a4\u30c8\u306b\u306f\u3001\u5c11\u306a\u304f\u3068\u30821\u4eba\u306e\u30b5\u30a4\u30c8\u30fb\u30de\u30cd\u30fc\u30b8\u30e3\u304c\u5fc5\u8981\u3067\u3059\u3002 \u73fe\u57281\u4eba\u3057\u304b\u30b5\u30a4\u30c8\u30fb\u30de\u30cd\u30fc\u30b8\u30e3\u304c\u3044\u306a\u3044\u305f\u3081\u3001\u30b5\u30a4\u30c8\u4f1a\u54e1\u304b\u3089 {0} \u3092\u53d6\u308a\u5916\u3059\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002 +site_service.can_not_remove_membership=\u73fe\u5728\u306e\u30e6\u30fc\u30b6\u306b\u306f\u3001\u30b5\u30a4\u30c8 {0} \u306e\u4f1a\u54e1\u8a73\u7d30\u3092\u524a\u9664\u3059\u308b\u305f\u3081\u306e\u5341\u5206\u306a\u6a29\u9650\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +site_service.do_not_change_manager=\u30b5\u30a4\u30c8\u306b\u306f\u3001\u5c11\u306a\u304f\u3068\u30821\u4eba\u306e\u30b5\u30a4\u30c8\u30fb\u30de\u30cd\u30fc\u30b8\u30e3\u304c\u5fc5\u8981\u3067\u3059\u3002 \u73fe\u57281\u4eba\u3057\u304b\u30b5\u30a4\u30c8\u30fb\u30de\u30cd\u30fc\u30b8\u30e3\u304c\u3044\u306a\u3044\u305f\u3081\u3001{0} \u306e\u5f79\u5272\u3092\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\u3002 +site_service.can_not_change_membership=\u73fe\u5728\u306e\u30e6\u30fc\u30b6\u306b\u306f\u3001\u30b5\u30a4\u30c8 {0} \u306e\u4f1a\u54e1\u8a73\u7d30\u3092\u5909\u66f4\u3059\u308b\u305f\u3081\u306e\u5341\u5206\u306a\u6a29\u9650\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +site_service.site_container_not_folder=\u30b5\u30a4\u30c8\u30b3\u30f3\u30c6\u30ca {0} \u304c\u30d5\u30a9\u30eb\u30c0\u3092\u53c2\u7167\u3057\u3066\u3044\u307e\u305b\u3093\u3002 \ No newline at end of file diff --git a/config/alfresco/messages/slingshot.properties b/config/alfresco/messages/slingshot.properties new file mode 100644 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/slingshot.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/slingshot_de.properties b/config/alfresco/messages/slingshot_de.properties new file mode 100755 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/slingshot_de.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/slingshot_es.properties b/config/alfresco/messages/slingshot_es.properties new file mode 100755 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/slingshot_es.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/slingshot_fr.properties b/config/alfresco/messages/slingshot_fr.properties new file mode 100755 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/slingshot_fr.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/slingshot_it.properties b/config/alfresco/messages/slingshot_it.properties new file mode 100755 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/slingshot_it.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/slingshot_ja.properties b/config/alfresco/messages/slingshot_ja.properties new file mode 100755 index 0000000000..f3a8a748b1 --- /dev/null +++ b/config/alfresco/messages/slingshot_ja.properties @@ -0,0 +1 @@ +# dummy file to allow repository context (activities feed notification) to start without slingshot - will be overwritten by build \ No newline at end of file diff --git a/config/alfresco/messages/system-messages.properties b/config/alfresco/messages/system-messages.properties index 6431e00ac9..f19864e74b 100644 --- a/config/alfresco/messages/system-messages.properties +++ b/config/alfresco/messages/system-messages.properties @@ -29,3 +29,24 @@ system.locks.err.excl_lock_exists=Failed to get lock ''{0}'' using token ''{1}'' # Multi-Tenant system.mt.warn.upgrade_mt_admin_context=Please update your alfresco/extension/mt/mt-admin-context.xml (to use latest alfresco/extension/mt/mt-admin-context.xml.sample). + +# Usages +system.usage.err.no_txn=RepoUsageComponent must be called in a transaction. +system.usage.err.no_txn_readwrite=RepoUsageComponent must be called in a read-write transaction. +system.usage.warn.limit_users_approached=The allowable user limit of {0} is being approached. There are {1} users in the system. +system.usage.warn.limit_users_reached=The allowable user limit of {0} has been reached. There are {1} users in the system. +system.usage.warn.limit_documents_approached=The allowable document limit of {0} is being approached. There are {1} documents in the system. +system.usage.warn.limit_documents_reached=The allowable document limit of {0} has been reached. There are {1} documents in the system. +system.usage.err.limit_users_exceeded=The allowable user limit of {0} has been exceeded. There are currently {1} users in the system. +system.usage.err.limit_users_exceeded_verbose=The allowable user limit of {0} has been exceeded. Users added: {1} +system.usage.err.limit_documents_exceeded=The allowable document limit of {0} has been exceeded. There are currently {1} documents in the system. +system.usage.err.limit_license_expiring=The Alfresco license will expire in {0} days. +system.usage.err.limit_license_expired=The Alfresco license has expired. + +# License +system.license.msg.reloaded=The Alfresco license has been reloaded. +system.license.err.reloadFailed=Failed to reload license: {0} + +# Startup message +system.info.startup=Alfresco started ({0}{1}{2}). Current version: {3} schema {4}. Originally installed version: {5} schema {6}. +system.warn.readonly=Alfresco is currently in read-only mode. Please check your license. \ No newline at end of file diff --git a/config/alfresco/messages/system-messages_de.properties b/config/alfresco/messages/system-messages_de.properties index b84511f727..15ba99d73c 100755 --- a/config/alfresco/messages/system-messages_de.properties +++ b/config/alfresco/messages/system-messages_de.properties @@ -29,3 +29,24 @@ system.locks.err.excl_lock_exists=Failed to get lock ''{0}'' using token ''{1}'' # Multi-Tenant system.mt.warn.upgrade_mt_admin_context=Please update your alfresco/extension/mt/mt-admin-context.xml (to use latest alfresco/extension/mt/mt-admin-context.xml.sample). + +# Usages +system.usage.err.no_txn=RepoUsageComponent must be called in a transaction. +system.usage.err.no_txn_readwrite=RepoUsageComponent must be called in a read-write transaction. +system.usage.warn.limit_users_approached=The allowable user limit of {0} is being approached. There are {1} users in the system. +system.usage.warn.limit_users_reached=The allowable user limit of {0} has been reached. There are {1} users in the system. +system.usage.warn.limit_documents_approached=The allowable document limit of {0} is being approached. There are {1} documents in the system. +system.usage.warn.limit_documents_reached=The allowable document limit of {0} has been reached. There are {1} documents in the system. +system.usage.err.limit_users_exceeded=The allowable user limit of {0} has been exceeded. There are currently {1} users in the system. +system.usage.err.limit_users_exceeded_verbose=The allowable user limit of {0} has been exceeded. Users added: {1} +system.usage.err.limit_documents_exceeded=The allowable document limit of {0} has been exceeded. There are currently {1} documents in the system. +system.usage.err.limit_license_expiring=The Alfresco license will expire in {0} days. +system.usage.err.limit_license_expired=The Alfresco license has expired. + +# License +system.license.msg.reloaded=The Alfresco license has been reloaded. +system.license.err.reloadFailed=Failed to reload license: {0} + +# Startup message +system.info.startup=Alfresco started ({0}{1}{2}). Current version: {3} schema {4}. Originally installed version: {5} schema {6}. +system.warn.readonly=Alfresco is currently in read-only mode. Please check your license. \ No newline at end of file diff --git a/config/alfresco/messages/system-messages_es.properties b/config/alfresco/messages/system-messages_es.properties index b84511f727..15ba99d73c 100755 --- a/config/alfresco/messages/system-messages_es.properties +++ b/config/alfresco/messages/system-messages_es.properties @@ -29,3 +29,24 @@ system.locks.err.excl_lock_exists=Failed to get lock ''{0}'' using token ''{1}'' # Multi-Tenant system.mt.warn.upgrade_mt_admin_context=Please update your alfresco/extension/mt/mt-admin-context.xml (to use latest alfresco/extension/mt/mt-admin-context.xml.sample). + +# Usages +system.usage.err.no_txn=RepoUsageComponent must be called in a transaction. +system.usage.err.no_txn_readwrite=RepoUsageComponent must be called in a read-write transaction. +system.usage.warn.limit_users_approached=The allowable user limit of {0} is being approached. There are {1} users in the system. +system.usage.warn.limit_users_reached=The allowable user limit of {0} has been reached. There are {1} users in the system. +system.usage.warn.limit_documents_approached=The allowable document limit of {0} is being approached. There are {1} documents in the system. +system.usage.warn.limit_documents_reached=The allowable document limit of {0} has been reached. There are {1} documents in the system. +system.usage.err.limit_users_exceeded=The allowable user limit of {0} has been exceeded. There are currently {1} users in the system. +system.usage.err.limit_users_exceeded_verbose=The allowable user limit of {0} has been exceeded. Users added: {1} +system.usage.err.limit_documents_exceeded=The allowable document limit of {0} has been exceeded. There are currently {1} documents in the system. +system.usage.err.limit_license_expiring=The Alfresco license will expire in {0} days. +system.usage.err.limit_license_expired=The Alfresco license has expired. + +# License +system.license.msg.reloaded=The Alfresco license has been reloaded. +system.license.err.reloadFailed=Failed to reload license: {0} + +# Startup message +system.info.startup=Alfresco started ({0}{1}{2}). Current version: {3} schema {4}. Originally installed version: {5} schema {6}. +system.warn.readonly=Alfresco is currently in read-only mode. Please check your license. \ No newline at end of file diff --git a/config/alfresco/messages/system-messages_fr.properties b/config/alfresco/messages/system-messages_fr.properties index b84511f727..15ba99d73c 100755 --- a/config/alfresco/messages/system-messages_fr.properties +++ b/config/alfresco/messages/system-messages_fr.properties @@ -29,3 +29,24 @@ system.locks.err.excl_lock_exists=Failed to get lock ''{0}'' using token ''{1}'' # Multi-Tenant system.mt.warn.upgrade_mt_admin_context=Please update your alfresco/extension/mt/mt-admin-context.xml (to use latest alfresco/extension/mt/mt-admin-context.xml.sample). + +# Usages +system.usage.err.no_txn=RepoUsageComponent must be called in a transaction. +system.usage.err.no_txn_readwrite=RepoUsageComponent must be called in a read-write transaction. +system.usage.warn.limit_users_approached=The allowable user limit of {0} is being approached. There are {1} users in the system. +system.usage.warn.limit_users_reached=The allowable user limit of {0} has been reached. There are {1} users in the system. +system.usage.warn.limit_documents_approached=The allowable document limit of {0} is being approached. There are {1} documents in the system. +system.usage.warn.limit_documents_reached=The allowable document limit of {0} has been reached. There are {1} documents in the system. +system.usage.err.limit_users_exceeded=The allowable user limit of {0} has been exceeded. There are currently {1} users in the system. +system.usage.err.limit_users_exceeded_verbose=The allowable user limit of {0} has been exceeded. Users added: {1} +system.usage.err.limit_documents_exceeded=The allowable document limit of {0} has been exceeded. There are currently {1} documents in the system. +system.usage.err.limit_license_expiring=The Alfresco license will expire in {0} days. +system.usage.err.limit_license_expired=The Alfresco license has expired. + +# License +system.license.msg.reloaded=The Alfresco license has been reloaded. +system.license.err.reloadFailed=Failed to reload license: {0} + +# Startup message +system.info.startup=Alfresco started ({0}{1}{2}). Current version: {3} schema {4}. Originally installed version: {5} schema {6}. +system.warn.readonly=Alfresco is currently in read-only mode. Please check your license. \ No newline at end of file diff --git a/config/alfresco/messages/system-messages_it.properties b/config/alfresco/messages/system-messages_it.properties index b84511f727..15ba99d73c 100755 --- a/config/alfresco/messages/system-messages_it.properties +++ b/config/alfresco/messages/system-messages_it.properties @@ -29,3 +29,24 @@ system.locks.err.excl_lock_exists=Failed to get lock ''{0}'' using token ''{1}'' # Multi-Tenant system.mt.warn.upgrade_mt_admin_context=Please update your alfresco/extension/mt/mt-admin-context.xml (to use latest alfresco/extension/mt/mt-admin-context.xml.sample). + +# Usages +system.usage.err.no_txn=RepoUsageComponent must be called in a transaction. +system.usage.err.no_txn_readwrite=RepoUsageComponent must be called in a read-write transaction. +system.usage.warn.limit_users_approached=The allowable user limit of {0} is being approached. There are {1} users in the system. +system.usage.warn.limit_users_reached=The allowable user limit of {0} has been reached. There are {1} users in the system. +system.usage.warn.limit_documents_approached=The allowable document limit of {0} is being approached. There are {1} documents in the system. +system.usage.warn.limit_documents_reached=The allowable document limit of {0} has been reached. There are {1} documents in the system. +system.usage.err.limit_users_exceeded=The allowable user limit of {0} has been exceeded. There are currently {1} users in the system. +system.usage.err.limit_users_exceeded_verbose=The allowable user limit of {0} has been exceeded. Users added: {1} +system.usage.err.limit_documents_exceeded=The allowable document limit of {0} has been exceeded. There are currently {1} documents in the system. +system.usage.err.limit_license_expiring=The Alfresco license will expire in {0} days. +system.usage.err.limit_license_expired=The Alfresco license has expired. + +# License +system.license.msg.reloaded=The Alfresco license has been reloaded. +system.license.err.reloadFailed=Failed to reload license: {0} + +# Startup message +system.info.startup=Alfresco started ({0}{1}{2}). Current version: {3} schema {4}. Originally installed version: {5} schema {6}. +system.warn.readonly=Alfresco is currently in read-only mode. Please check your license. \ No newline at end of file diff --git a/config/alfresco/messages/system-messages_ja.properties b/config/alfresco/messages/system-messages_ja.properties new file mode 100755 index 0000000000..b84511f727 --- /dev/null +++ b/config/alfresco/messages/system-messages_ja.properties @@ -0,0 +1,31 @@ +# System-related messages + +system.err.property_not_set=Property ''{0}'' has not been set: {1} ({2}) +system.err.duplicate_name=Duplicate child name not allowed: {0} + +# Bootstrap configuration check messages + +system.config_check.warn.dir_root=The Alfresco ''dir.root'' property is set to a relative path ''{0}''. ''dir.root'' should be overridden to point to a specific folder. +system.config_check.msg.dir_root=The Alfresco root data directory (''dir.root'') is: {0} +system.config_check.err.indexes.duplicate_root_node=The store ''{0}'' has a duplicate root node entry. +system.config_check.err.missing_index=CONTENT INTEGRITY ERROR: Indexes not found for {0} stores. +system.config_check.err.missing_content=CONTENT INTEGRITY ERROR: System content not found in content store. +system.config_check.err.fix_dir_root=Ensure that the ''dir.root'' property is pointing to the correct data location. +system.config_check.msg.howto_index_recover=You may set 'index.recovery.mode=FULL' if you need to rebuild the indexes. +system.config_check.warn.starting_with_errors=Alfresco is starting with errors. + +# OpenOffice +system.openoffice.info.connection_verified=The connection to OpenOffice has been established. +system.openoffice.err.connection_failed=An initial OpenOffice connection could not be established. +system.openoffice.err.connection_lost=The OpenOffice connection has been lost. +system.openoffice.err.connection_remade=The OpenOffice connection was re-established. + +# Locks +system.locks.err.failed_to_acquire_lock=Failed to get lock ''{0}'' using token ''{1}''. +system.locks.err.lock_resource_missing=Failed to manipulate lock ''{0}'' using token ''{1}''. The lock resource no longer exists. +system.locks.err.lock_update_count=Failed to update lock ''{0}'' using token ''{1}''. {2} locks were updated when {3} should have been. +system.locks.err.failed_to_release_lock=Failed to release lock ''{0}'' using token ''{1}''. The lock has expired and been taken by another process. +system.locks.err.excl_lock_exists=Failed to get lock ''{0}'' using token ''{1}''. An exclusive lock exists: {2} + +# Multi-Tenant +system.mt.warn.upgrade_mt_admin_context=Please update your alfresco/extension/mt/mt-admin-context.xml (to use latest alfresco/extension/mt/mt-admin-context.xml.sample). diff --git a/config/alfresco/messages/system-model_de.properties b/config/alfresco/messages/system-model_de.properties index cf6501a503..90cf92e40d 100755 --- a/config/alfresco/messages/system-model_de.properties +++ b/config/alfresco/messages/system-model_de.properties @@ -41,6 +41,6 @@ sys_systemmodel.property.sys_archivedBy.title=Archiviert von sys_systemmodel.property.sys_archivedBy.description=Archiviert von sys_systemmodel.property.sys_archivedDate.title=Archivierungsdatum sys_systemmodel.property.sys_archivedDate.description=Archivierungsdatum -sys_systemmodel.property.sys_archivedOriginalPath.title=Urspr\u00fcngliche Speicherstelle -sys_systemmodel.property.sys_archivedOriginalPath.description=Urspr\u00fcngliche Speicherstelle +sys_systemmodel.property.sys_archivedOriginalPath.title=Urspr\u00fcnglicher Speicherort +sys_systemmodel.property.sys_archivedOriginalPath.description=Urspr\u00fcnglicher Speicherort diff --git a/config/alfresco/messages/system-model_ja.properties b/config/alfresco/messages/system-model_ja.properties new file mode 100755 index 0000000000..1dc3c3f980 --- /dev/null +++ b/config/alfresco/messages/system-model_ja.properties @@ -0,0 +1,45 @@ +# Display labels for System Model + +sys_systemmodel.description=Alfresco System Model + +sys_systemmodel.type.sys_base.title=\u30d9\u30fc\u30b9 +sys_systemmodel.type.sys_base.description=\u30d9\u30fc\u30b9 + +sys_systemmodel.type.sys_container.title=\u30b3\u30f3\u30c6\u30ca +sys_systemmodel.type.sys_container.description=\u30b3\u30f3\u30c6\u30ca +sys_systemmodel.association.sys_children.title=\u5b50 +sys_systemmodel.association.sys_children.description=\u5b50 + +sys_systemmodel.type.sys_store_root.title=\u30b9\u30c8\u30a2\u30eb\u30fc\u30c8 +sys_systemmodel.type.sys_store_root.description=\u30b9\u30c8\u30a2\u30eb\u30fc\u30c8 + +sys_systemmodel.type.sys_reference.title=\u53c2\u7167 +sys_systemmodel.type.sys_reference.description=\u53c2\u7167 +sys_systemmodel.property.sys_reference.title=\u53c2\u7167 +sys_systemmodel.property.sys_reference.description=\u53c2\u7167 + +sys_systemmodel.property.sys_locale.title=\u8a00\u8a9e\u30fb\u5730\u57df +sys_systemmodel.property.sys_locale.description=\u8a00\u8a9e\u30fb\u5730\u57df + +sys_systemmodel.aspect.aspect_root.title=\u30eb\u30fc\u30c8 +sys_systemmodel.aspect.aspect_root.description=\u30eb\u30fc\u30c8 + +sys_systemmodel.aspect.sys_referenceable.title=\u53c2\u7167\u53ef\u80fd +sys_systemmodel.aspect.sys_referenceable.description=\u53c2\u7167\u53ef\u80fd +sys_systemmodel.property.sys_store-protocol.title=\u30b9\u30c8\u30a2\u30d7\u30ed\u30c8\u30b3\u30eb +sys_systemmodel.property.sys_store-protocol.description=\u30b9\u30c8\u30a2\u30d7\u30ed\u30c8\u30b3\u30eb +sys_systemmodel.property.sys_store-identifier.title=\u30b9\u30c8\u30a2ID +sys_systemmodel.property.sys_store-identifier.description=\u30b9\u30c8\u30a2ID +sys_systemmodel.property.sys_node-uuid.title=\u30ce\u30fc\u30c9ID +sys_systemmodel.property.sys_node-uuid.description=\u30ce\u30fc\u30c9ID +sys_systemmodel.property.sys_node-dbid.title=\u30ce\u30fc\u30c9DB\u8b58\u5225\u5b50 +sys_systemmodel.property.sys_node-dbid.description=\u30ce\u30fc\u30c9DB\u8b58\u5225\u5b50 + +sys_systemmodel.aspect.sys_archived.title=\u30a2\u30fc\u30ab\u30a4\u30d6\u6e08 +sys_systemmodel.aspect.sys_archived.description=\u30a2\u30fc\u30ab\u30a4\u30d6 \u30ce\u30fc\u30c9 +sys_systemmodel.property.sys_archivedBy.title=\u30a2\u30fc\u30ab\u30a4\u30d6\u5b9f\u884c\u8005 +sys_systemmodel.property.sys_archivedBy.description=\u30a2\u30fc\u30ab\u30a4\u30d6\u5b9f\u884c\u8005 +sys_systemmodel.property.sys_archivedDate.title=\u30a2\u30fc\u30ab\u30a4\u30d6\u65e5\u4ed8 +sys_systemmodel.property.sys_archivedDate.description=\u30a2\u30fc\u30ab\u30a4\u30d6\u65e5\u4ed8 +sys_systemmodel.property.sys_archivedOriginalPath.title=\u5143\u306e\u5834\u6240 +sys_systemmodel.property.sys_archivedOriginalPath.description=\u5143\u306e\u5834\u6240 diff --git a/config/alfresco/messages/template-service_ja.properties b/config/alfresco/messages/template-service_ja.properties new file mode 100755 index 0000000000..c5bf67b7ee --- /dev/null +++ b/config/alfresco/messages/template-service_ja.properties @@ -0,0 +1,5 @@ +# Template Service externalised display strings + +error_no_template=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 ''{0}'' \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 \u30b7\u30b9\u30c6\u30e0\u7ba1\u7406\u8005\u306b\u304a\u554f\u3044\u5408\u308f\u305b\u304f\u3060\u3055\u3044\u3002 +error_template_fail=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 ''{0}'' \u306e\u51e6\u7406\u4e2d\u306e\u30a8\u30e9\u30fc\u3067\u3059\u3002 \u30b7\u30b9\u30c6\u30e0\u7ba1\u7406\u8005\u306b\u304a\u554f\u3044\u5408\u308f\u305b\u304f\u3060\u3055\u3044\u3002 +error_template_io=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8 ''{0}'' \u306e\u51e6\u7406\u4e2d\u306eI/O\u30a8\u30e9\u30fc\u3067\u3059\u3002 \u30b7\u30b9\u30c6\u30e0\u7ba1\u7406\u8005\u306b\u304a\u554f\u3044\u5408\u308f\u305b\u304f\u3060\u3055\u3044\u3002 diff --git a/config/alfresco/messages/templates-messages_de.properties b/config/alfresco/messages/templates-messages_de.properties index 08282757bc..61dd23bfab 100755 --- a/config/alfresco/messages/templates-messages_de.properties +++ b/config/alfresco/messages/templates-messages_de.properties @@ -33,7 +33,7 @@ templates.recent_docs.modified_date=Datum der \u00c4nderung #my_summary.ftl templates.my_summary.name=Name: templates.my_summary.user=Benutzer: -templates.my_summary.home_space_location=Speicherstelle des Home-Arbeitsbereichs: +templates.my_summary.home_space_location=Speicherort des Home-Arbeitsbereichs: templates.my_summary.items_in_home_space=Elemente im Home-Arbeitsbereich: templates.my_summary.items_in_company_space=Elemente im Firmen-Arbeitsbereich: @@ -53,7 +53,7 @@ templates.localizable.no=Nein templates.localizable.no_document_found=Kein Dokument gefunden! #general_example.ftl -templates.general_example.example_template_start= +templates.general_example.example_template_start======Beginn der Mustervorlage===== templates.general_example.company_home_space=Firmen Home-Arbeitsbereich templates.general_example.my_home_space=Mein Home-Arbeitsbereich: templates.general_example.company_home_children_count=Anzahl der Kinder des Firmen-Home: @@ -64,7 +64,7 @@ templates.general_example.list_of_child_spaces_in_my_home_space=Liste der Kinder templates.general_example.path=Pfad: templates.general_example.list_of_docs_in_my_home_space=Liste der Dokumente in Meinem Home-Arbeitsbereich (nur Text, Inhalt wird inline, JPG-Bilder als Miniaturansichten angezeigt): templates.general_example.assoc_example=Assoziierungsbeispiel -templates.general_example.example_template_end= +templates.general_example.example_template_end======Ende der Mustervorlage===== #doc_info.ftl templates.doc_info.current_document_info=Info zum aktuellen Dokument: diff --git a/config/alfresco/messages/templates-messages_fr.properties b/config/alfresco/messages/templates-messages_fr.properties index 13770e31e0..dfb92b86ef 100755 --- a/config/alfresco/messages/templates-messages_fr.properties +++ b/config/alfresco/messages/templates-messages_fr.properties @@ -53,7 +53,7 @@ templates.localizable.no=Non templates.localizable.no_document_found=Aucun document\u00a0! #general_example.ftl -templates.general_example.example_template_start= +templates.general_example.example_template_start======D\u00e9but du mod\u00e8le d'exemple===== templates.general_example.company_home_space=Espace personnel de l'entreprise\u00a0: templates.general_example.my_home_space=Mon espace personnel\u00a0: templates.general_example.company_home_children_count=Compte enfant de la racine de l'entrep\u00f4t\u00a0: @@ -64,7 +64,7 @@ templates.general_example.list_of_child_spaces_in_my_home_space=Liste des espace templates.general_example.path=Chemin: templates.general_example.list_of_docs_in_my_home_space=Liste des documents dans mon espace personnel (contenu uniquement textuel affich\u00e9 en ligne, images\u00a0JPG affich\u00e9es sous la forme de vignettes)\u00a0: templates.general_example.assoc_example=Exemple d'assoc.\u00a0: -templates.general_example.example_template_end= +templates.general_example.example_template_end======Fin du mod\u00e8le d'exemple===== #doc_info.ftl templates.doc_info.current_document_info=Informations du document actuel\u00a0: diff --git a/config/alfresco/messages/templates-messages_it.properties b/config/alfresco/messages/templates-messages_it.properties index f73ffb0bdc..7dc9ae4b74 100755 --- a/config/alfresco/messages/templates-messages_it.properties +++ b/config/alfresco/messages/templates-messages_it.properties @@ -48,12 +48,12 @@ templates.my_docs.yes=S\u00ec #localizable.ftl templates.localizable.localisable=Localizzabile: templates.localizable.yes=S\u00ec -templates.localizable.locale=Impostazioni locali: +templates.localizable.locale=Locali: templates.localizable.no=No templates.localizable.no_document_found=Nessun documento trovato #general_example.ftl -templates.general_example.example_template_start= +templates.general_example.example_template_start======Inicio del esempio del modello===== templates.general_example.company_home_space=Spazio di homepage dell'azienda: templates.general_example.my_home_space=Il mio spazio di homepage: templates.general_example.company_home_children_count=Conteggio dei figli nella homepage dell'azienda: @@ -64,7 +64,7 @@ templates.general_example.list_of_child_spaces_in_my_home_space=Elenco degli spa templates.general_example.path=Percorso: templates.general_example.list_of_docs_in_my_home_space=Elenco dei documenti nel mio spazio di homepage (il contenuto di solo testo viene mostrato in linea, le immagini JPG vengono mostrate come miniature): templates.general_example.assoc_example=Esempio di associazione: -templates.general_example.example_template_end= +templates.general_example.example_template_end======Fine del esempio del modello===== #doc_info.ftl templates.doc_info.current_document_info=Info documento attuale: @@ -78,4 +78,4 @@ templates.doc_info.counter=Contatore: templates.doc_info.aspects=Aspetti: templates.doc_info.assocs=Associazioni: templates.doc_info.properties=Propriet\u00e0: -templates.doc_info.no_document_found=Nessun documento trovato +templates.doc_info.no_document_found=Nessun documento trovato! diff --git a/config/alfresco/messages/templates-messages_ja.properties b/config/alfresco/messages/templates-messages_ja.properties new file mode 100755 index 0000000000..82338a2ca7 --- /dev/null +++ b/config/alfresco/messages/templates-messages_ja.properties @@ -0,0 +1,81 @@ +#translatable.ftl +templates.translatable.translatable=\u7ffb\u8a33\u53ef\u80fd: +templates.translatable.yes=\u306f\u3044 +templates.translatable.no=\u3044\u3044\u3048 +templates.translatable.no_document_found=\u6587\u66f8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 + +#show_audit.ftl +templates.show_audit.current_document_audit_info=\u73fe\u5728\u306e\u6587\u66f8\u306e\u76e3\u67fb\u60c5\u5831 +templates.show_audit.name=\u540d\u524d: +templates.show_audit.user_name=\u30e6\u30fc\u30b6\u540d +templates.show_audit.application=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 +templates.show_audit.service=\u30b5\u30fc\u30d3\u30b9 +templates.show_audit.method=\u65b9\u6cd5 +templates.show_audit.timestamp=\u30bf\u30a4\u30e0\u30b9\u30bf\u30f3\u30d7 +templates.show_audit.failed=\u5931\u6557 +templates.show_audit.message=\u30e1\u30c3\u30bb\u30fc\u30b8 +templates.show_audit.arg_1=\u5f15\u6570 1 +templates.show_audit.arg_2=\u5f15\u6570 2 +templates.show_audit.arg_3=\u5f15\u6570 3 +templates.show_audit.arg_4=\u5f15\u6570 4 +templates.show_audit.arg_5=\u5f15\u6570 5 +templates.show_audit.return=\u623b\u308a\u5024 +templates.show_audit.thowable=Throwable +templates.show_audit.tx=TX +templates.show_audit.current_space_audit_info=\u73fe\u5728\u306e\u30b9\u30da\u30fc\u30b9\u306e\u76e3\u67fb\u60c5\u5831: + +#recent_docs.ftl +templates.recent_docs.documents_created_or_modified_in_the_last_week=\u5148\u9031\u4f5c\u6210\u3082\u3057\u304f\u306f\u5909\u66f4\u3055\u308c\u305f\u6587\u66f8 +templates.recent_docs.name=\u540d\u524d +templates.recent_docs.created_date=\u4f5c\u6210\u65e5 +templates.recent_docs.modified_date=\u5909\u66f4\u65e5 + +#my_summary.ftl +templates.my_summary.name=\u540d\u524d: +templates.my_summary.user=\u30e6\u30fc\u30b6: +templates.my_summary.home_space_location=\u30db\u30fc\u30e0\u30b9\u30da\u30fc\u30b9\u30ed\u30b1\u30fc\u30b7\u30e7\u30f3: +templates.my_summary.items_in_home_space=\u30db\u30fc\u30e0\u30b9\u30da\u30fc\u30b9\u306e\u30a2\u30a4\u30c6\u30e0: +templates.my_summary.items_in_company_space=\u30ab\u30f3\u30d1\u30cb\u30fc\u30b9\u30da\u30fc\u30b9\u306e\u30a2\u30a4\u30c6\u30e0: + +#my_docs.ftl +templates.my_docs.name=\u540d\u524d +templates.my_docs.size=\u30b5\u30a4\u30ba +templates.my_docs.modified.date=\u5909\u66f4\u65e5 +templates.my_docs.locked=\u30ed\u30c3\u30af\u6e08 +templates.my_docs.kb=KB +templates.my_docs.yes=\u306f\u3044 + +#localizable.ftl +templates.localizable.localisable=\u30ed\u30fc\u30ab\u30e9\u30a4\u30ba\u53ef\u80fd: +templates.localizable.yes=\u306f\u3044 +templates.localizable.locale=\u8a00\u8a9e\u30fb\u5730\u57df: +templates.localizable.no=\u3044\u3044\u3048 +templates.localizable.no_document_found=\u6587\u66f8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\uff01 + +#general_example.ftl +templates.general_example.example_template_start======\u30b5\u30f3\u30d7\u30eb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u958b\u59cb===== +templates.general_example.company_home_space=\u30ab\u30f3\u30d1\u30cb\u30fc\u30db\u30fc\u30e0\u30b9\u30da\u30fc\u30b9: +templates.general_example.my_home_space=\u30de\u30a4\u30db\u30fc\u30e0\u30b9\u30da\u30fc\u30b9: +templates.general_example.company_home_children_count=\u30ab\u30f3\u30d1\u30cb\u30fc\u30db\u30fc\u30e0\u306e\u5b50\u306e\u30ab\u30a6\u30f3\u30c8: +templates.general_example.company_home_first_child_node_name=\u30ab\u30f3\u30d1\u30cb\u30fc\u30db\u30fc\u30e0\u306e\u6700\u521d\u306e\u5b50\u30ce\u30fc\u30c9\u540d: +templates.general_example.current_document_name=\u73fe\u5728\u306e\u6587\u66f8\u540d: +templates.general_example.current_space_name=\u73fe\u5728\u306e\u30b9\u30da\u30fc\u30b9\u540d: +templates.general_example.list_of_child_spaces_in_my_home_space=\u30db\u30fc\u30e0\u30b9\u30da\u30fc\u30b9\u306e\u30b5\u30d6\u30b9\u30da\u30fc\u30b9\u306e\u30ea\u30b9\u30c8: +templates.general_example.path=\u30d1\u30b9: +templates.general_example.list_of_docs_in_my_home_space=\u30de\u30a4\u30db\u30fc\u30e0\u30b9\u30da\u30fc\u30b9\u306e\u6587\u66f8\u306e\u30ea\u30b9\u30c8 \uff08\u30c6\u30ad\u30b9\u30c8\u306e\u307f\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u306f\u30a4\u30f3\u30e9\u30a4\u30f3\u8868\u793a\u3001JPG\u753b\u50cf\u306f\u30b5\u30e0\u30cd\u30a4\u30eb\u8868\u793a\uff09: +templates.general_example.assoc_example=\u95a2\u9023\u30b5\u30f3\u30d7\u30eb: +templates.general_example.example_template_end======\u30b5\u30f3\u30d7\u30eb\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u7d42\u4e86===== + +#doc_info.ftl +templates.doc_info.current_document_info=\u73fe\u5728\u306e\u6587\u66f8\u60c5\u5831: +templates.doc_info.name=\u540d\u524d: +templates.doc_info.ref=\u53c2\u7167: +templates.doc_info.type=\u30bf\u30a4\u30d7: +templates.doc_info.dbid=DBID: +templates.doc_info.content_url=\u30b3\u30f3\u30c6\u30f3\u30c4URL: +templates.doc_info.locked=\u30ed\u30c3\u30af\u6e08: +templates.doc_info.counter=\u30ab\u30a6\u30f3\u30bf: +templates.doc_info.aspects=\u30a2\u30b9\u30da\u30af\u30c8: +templates.doc_info.assocs=\u95a2\u9023: +templates.doc_info.properties=\u30d7\u30ed\u30d1\u30c6\u30a3: +templates.doc_info.no_document_found=\u6587\u66f8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\uff01 diff --git a/config/alfresco/messages/tenant-interpreter-help_ja.properties b/config/alfresco/messages/tenant-interpreter-help_ja.properties new file mode 100755 index 0000000000..8314d0f5e3 --- /dev/null +++ b/config/alfresco/messages/tenant-interpreter-help_ja.properties @@ -0,0 +1 @@ +tenant_console.help=alfresco/messages/tenant-interpreter-help.txt diff --git a/config/alfresco/messages/transfer-model.properties b/config/alfresco/messages/transfer-model.properties new file mode 100644 index 0000000000..f41b3fa067 --- /dev/null +++ b/config/alfresco/messages/transfer-model.properties @@ -0,0 +1,86 @@ +# Display labels for Transfer Model + +trx_applicationmodel.description=Alfresco Transfer Application Model + +trx_applicationmodel.type.trx_transferGroup.title=Transfer Group +trx_applicationmodel.type.trx_transferGroup.description=The definition of a transfer group + +trx_applicationmodel.type.trx_transferLock.title=Transfer Lock +trx_applicationmodel.type.trx_transferLock.description=Node type used to represent the transfer lock node + +trx_applicationmodel.type.trx_transferRecord.title=Transfer Record +trx_applicationmodel.type.trx_transferRecord.description=Node type used to record transfer information + +trx_applicationmodel.type.trx_tempTransferStore.title=Temp Transfer Store +trx_applicationmodel.type.trx_tempTransferStore.description=Node type used for storage of temporarily orphaned incoming nodes + +trx_applicationmodel.type.trx_transferReport.title=Transfer Report +trx_applicationmodel.type.trx_transferReport.description=Transfer Report + +trx_applicationmodel.type.trx_transferReportDest.title=Transfer Report From Destination +trx_applicationmodel.type.trx_transferReportDest.description=Transfer Report From Destination + +trx_applicationmodel.type.trx_transferTarget.title=Transfer Target +trx_applicationmodel.type.trx_transferTarget.description=The definition of a transfer target + +trx_applicationmodel.property.trx_endpointhost.title=Endpoint Host +trx_applicationmodel.property.trx_endpointhost.description=Endpoint Host + +trx_applicationmodel.property.trx_endpointport.title=Endpoint Port +trx_applicationmodel.property.trx_endpointport.description=Endpoint Port + +trx_applicationmodel.property.trx_endpointpath.title=Endpoint Path +trx_applicationmodel.property.trx_endpointpath.description=Endpoint Path + +trx_applicationmodel.property.trx_endpointprotocol.title=Endpoint Protocol +trx_applicationmodel.property.trx_endpointprotocol.description=Endpoint Protocol + +trx_applicationmodel.property.trx_enabled.title=Enabled +trx_applicationmodel.property.trx_enabled.description=Is this enabled or disabled + +trx_applicationmodel.property.trx_username.title=Username +trx_applicationmodel.property.trx_username.description=Username + +trx_applicationmodel.property.trx_password.title=Password +trx_applicationmodel.property.trx_password.description=Password + +trx_applicationmodel.property.trx_progressPosition.title=Progress Position +trx_applicationmodel.property.trx_progressPosition.description=Progress Position + +trx_applicationmodel.property.trx_progressEndpoint.title=Progress Endpoint +trx_applicationmodel.property.trx_progressEndpoint.description=Progress Endpoint + +trx_applicationmodel.property.trx_transferStatus.title=Transfer Status +trx_applicationmodel.property.trx_transferStatus.description=Transfer Status + +trx_applicationmodel.property.trx_transferError.title=Transfer Error +trx_applicationmodel.property.trx_transferError.description=Transfer Error + +trx_applicationmodel.property.trx_transferId.title=Transfer Id +trx_applicationmodel.property.trx_transferId.description=Transfer Id + +trx_applicationmodel.property.trx_invadedBy.title=Invaded By +trx_applicationmodel.property.trx_invadedBy.description=The repositories that have invaded this node + +trx_applicationmodel.property.trx_repositoryId.title=Source Repository. +trx_applicationmodel.property.trx_repositoryId.description=The repository id that this node originates from. + +trx_applicationmodel.property.trx_fromRepositoryId.title=From Repository Id +trx_applicationmodel.property.trx_fromContent.description=The content URLs transferred with this node + +trx_applicationmodel.association.trx_orphan.title=Transfer Orphan +trx_applicationmodel.association.trx_orphan.description=Transfer Orphan + +trx_applicationmodel.aspect.trx_transferRelated.title=Transfer Related +trx_applicationmodel.aspect.trx_transferRelated.description=Nodes with this aspect are related to a particular transfer. + +trx_applicationmodel.aspect.trx_enableable.title=Enableable +trx_applicationmodel.aspect.trx_enableable.description=Enableable + +trx_applicationmodel.aspect.trx_transferred.name=Transferred +trx_applicationmodel.aspect.trx_transferred.description=Nodes with this aspect have been transferred from one repository to another + +trx_applicationmodel.aspect.trx_alien.name=Transfer Alien +trx_applicationmodel.aspect.trx_alien.description=Nodes with this aspect are either alien nodes or have been invaded by other alien nodes + + diff --git a/config/alfresco/messages/transfer-model_de.properties b/config/alfresco/messages/transfer-model_de.properties new file mode 100755 index 0000000000..699fbd3a8a --- /dev/null +++ b/config/alfresco/messages/transfer-model_de.properties @@ -0,0 +1,86 @@ +# Display labels for Transfer Model + +trx_applicationmodel.description=Alfresco \u00dcbertragungsanwendungsmodell + +trx_applicationmodel.type.trx_transferGroup.title=\u00dcbertragungsgruppe +trx_applicationmodel.type.trx_transferGroup.description=Definition einer \u00dcbertragungsgruppe + +trx_applicationmodel.type.trx_transferLock.title=\u00dcbertragungssperre +trx_applicationmodel.type.trx_transferLock.description=Knoten-Typ, der f\u00fcr den \u00dcbertragungssperrknoten verwendet wurde + +trx_applicationmodel.type.trx_transferRecord.title=\u00dcbertragungsaufzeichnung +trx_applicationmodel.type.trx_transferRecord.description=Knoten-Typ, der f\u00fcr die Aufzeichnung der \u00dcbertragungsinformation verwendet wurde + +trx_applicationmodel.type.trx_tempTransferStore.title=Tempor\u00e4rer \u00dcbertragungsspeicher +trx_applicationmodel.type.trx_tempTransferStore.description=Knoten-Typ, der f\u00fcr die Speicherung tempor\u00e4r verwaister eingehender Knoten verwendet wird + +trx_applicationmodel.type.trx_transferReport.title=\u00dcbertragungsbericht +trx_applicationmodel.type.trx_transferReport.description=\u00dcbertragungsbericht + +trx_applicationmodel.type.trx_transferReportDest.title=Bericht von Ziel \u00fcbertragen +trx_applicationmodel.type.trx_transferReportDest.description=Bericht von Ziel \u00fcbertragen + +trx_applicationmodel.type.trx_transferTarget.title=Ziel\u00fcbertragung +trx_applicationmodel.type.trx_transferTarget.description=Definition eines \u00dcbertragungsziels + +trx_applicationmodel.property.trx_endpointhost.title=Endpunkt-Host +trx_applicationmodel.property.trx_endpointhost.description=Endpunkt-Host + +trx_applicationmodel.property.trx_endpointport.title=Endpunkt-Port +trx_applicationmodel.property.trx_endpointport.description=Endpunkt-Port + +trx_applicationmodel.property.trx_endpointpath.title=Endpunktpfad +trx_applicationmodel.property.trx_endpointpath.description=Endpunktpfad + +trx_applicationmodel.property.trx_endpointprotocol.title=Endpunktprotokoll +trx_applicationmodel.property.trx_endpointprotocol.description=Endpunktprotokoll + +trx_applicationmodel.property.trx_enabled.title=Aktiviert +trx_applicationmodel.property.trx_enabled.description=Ist dies aktiviert oder deaktiviert + +trx_applicationmodel.property.trx_username.title=Benutzername +trx_applicationmodel.property.trx_username.description=Benutzername + +trx_applicationmodel.property.trx_password.title=Passwort +trx_applicationmodel.property.trx_password.description=Passwort + +trx_applicationmodel.property.trx_progressPosition.title=Fortschrittsposition +trx_applicationmodel.property.trx_progressPosition.description=Fortschrittsposition + +trx_applicationmodel.property.trx_progressEndpoint.title=Fortschritts-Endpunkt +trx_applicationmodel.property.trx_progressEndpoint.description=Fortschritts-Endpunkt + +trx_applicationmodel.property.trx_transferStatus.title=\u00dcbertragungsstatus +trx_applicationmodel.property.trx_transferStatus.description=\u00dcbertragungsstatus + +trx_applicationmodel.property.trx_transferError.title=\u00dcbertragungsfehler +trx_applicationmodel.property.trx_transferError.description=\u00dcbertragungsfehler + +trx_applicationmodel.property.trx_transferId.title=\u00dcbertragungs-ID +trx_applicationmodel.property.trx_transferId.description=\u00dcbertragungs-ID + +trx_applicationmodel.property.trx_invadedBy.title=Invasion von +trx_applicationmodel.property.trx_invadedBy.description=Die Repositorys, die in diesen Knoten eingedrungen sind + +trx_applicationmodel.property.trx_repositoryId.title=Quell-Repository. +trx_applicationmodel.property.trx_repositoryId.description=Die Repository-ID, von der der Knoten stammt. + +trx_applicationmodel.property.trx_fromRepositoryId.title=Von Repository-ID +trx_applicationmodel.property.trx_fromContent.description=Die Inhalts-URLs, die mit diesem Knoten \u00fcbertragen werden + +trx_applicationmodel.association.trx_orphan.title=Verwaiste \u00fcbertragen +trx_applicationmodel.association.trx_orphan.description=Verwaiste \u00fcbertragen + +trx_applicationmodel.aspect.trx_transferRelated.title=Zugeh\u00f6rige \u00fcbertragen +trx_applicationmodel.aspect.trx_transferRelated.description=Knoten mit diesem Aspekt geh\u00f6ren zu einer bestimmten \u00dcbertragung. + +trx_applicationmodel.aspect.trx_enableable.title=Aktivierbar +trx_applicationmodel.aspect.trx_enableable.description=Aktivierbar + +trx_applicationmodel.aspect.trx_transferred.name=\u00dcbertragen +trx_applicationmodel.aspect.trx_transferred.description=Knoten mit diesem Aspekt wurden von einem Repository zu einem anderen \u00fcbertragen + +trx_applicationmodel.aspect.trx_alien.name=Fremde \u00fcbertragen +trx_applicationmodel.aspect.trx_alien.description=Knoten mit diesem Aspekt sind entweder fremde Knoten oder andere fremde Knoten sind eingedrungen + + diff --git a/config/alfresco/messages/transfer-model_es.properties b/config/alfresco/messages/transfer-model_es.properties new file mode 100755 index 0000000000..115e438659 --- /dev/null +++ b/config/alfresco/messages/transfer-model_es.properties @@ -0,0 +1,86 @@ +# Display labels for Transfer Model + +trx_applicationmodel.description=Modelo de aplicaci\u00f3n de transferencia Alfresco + +trx_applicationmodel.type.trx_transferGroup.title=Grupo de transferencia +trx_applicationmodel.type.trx_transferGroup.description=Definici\u00f3n de un grupo de transferencia + +trx_applicationmodel.type.trx_transferLock.title=Bloqueo de transferencia +trx_applicationmodel.type.trx_transferLock.description=Tipo de nodo utilizado para representar el nodo de bloqueo de transferencia + +trx_applicationmodel.type.trx_transferRecord.title=Registro de transferencia +trx_applicationmodel.type.trx_transferRecord.description=Tipo de nodo utilizado para registrar la informaci\u00f3n de la transferencia + +trx_applicationmodel.type.trx_tempTransferStore.title=Almac\u00e9n de transferencia temporal +trx_applicationmodel.type.trx_tempTransferStore.description=Tipo de nodo utilizado para almacenamiento de nodos de entrada temporalmente hu\u00e9rfanos + +trx_applicationmodel.type.trx_transferReport.title=Informe de transferencia +trx_applicationmodel.type.trx_transferReport.description=Informe de transferencia + +trx_applicationmodel.type.trx_transferReportDest.title=Informe de transferencia desde destino +trx_applicationmodel.type.trx_transferReportDest.description=Informe de transferencia desde destino + +trx_applicationmodel.type.trx_transferTarget.title=Destino de la transferencia +trx_applicationmodel.type.trx_transferTarget.description=Definici\u00f3n del destino de una transferencia + +trx_applicationmodel.property.trx_endpointhost.title=Servidor de destino +trx_applicationmodel.property.trx_endpointhost.description=Servidor de destino + +trx_applicationmodel.property.trx_endpointport.title=Puerto de destino +trx_applicationmodel.property.trx_endpointport.description=Puerto de destino + +trx_applicationmodel.property.trx_endpointpath.title=Ruta de destino +trx_applicationmodel.property.trx_endpointpath.description=Ruta de destino + +trx_applicationmodel.property.trx_endpointprotocol.title=Protocolo de destino +trx_applicationmodel.property.trx_endpointprotocol.description=Protocolo de destino + +trx_applicationmodel.property.trx_enabled.title=Activada +trx_applicationmodel.property.trx_enabled.description=Est\u00e1 activada o desactivada + +trx_applicationmodel.property.trx_username.title=Nombre de usuario +trx_applicationmodel.property.trx_username.description=Nombre de usuario + +trx_applicationmodel.property.trx_password.title=Contrase\u00f1a +trx_applicationmodel.property.trx_password.description=Contrase\u00f1a + +trx_applicationmodel.property.trx_progressPosition.title=Posici\u00f3n de progreso +trx_applicationmodel.property.trx_progressPosition.description=Posici\u00f3n de progreso + +trx_applicationmodel.property.trx_progressEndpoint.title=Punto final de progreso +trx_applicationmodel.property.trx_progressEndpoint.description=Punto final de progreso + +trx_applicationmodel.property.trx_transferStatus.title=Estado de la transferencia +trx_applicationmodel.property.trx_transferStatus.description=Estado de la transferencia + +trx_applicationmodel.property.trx_transferError.title=Error de transferencia +trx_applicationmodel.property.trx_transferError.description=Error de transferencia + +trx_applicationmodel.property.trx_transferId.title=Identificador de transferencia +trx_applicationmodel.property.trx_transferId.description=Identificador de transferencia + +trx_applicationmodel.property.trx_invadedBy.title=Invadido por +trx_applicationmodel.property.trx_invadedBy.description=Los repositorios que han invadido este nodo + +trx_applicationmodel.property.trx_repositoryId.title=Repositorio de origen. +trx_applicationmodel.property.trx_repositoryId.description=El identificador del repositorio desde donde se origina este nodo. + +trx_applicationmodel.property.trx_fromRepositoryId.title=Desde identificador de repositorio +trx_applicationmodel.property.trx_fromContent.description=Las URL de contenido transferidas con este nodo + +trx_applicationmodel.association.trx_orphan.title=Hu\u00e9rfano de transferencia +trx_applicationmodel.association.trx_orphan.description=Hu\u00e9rfano de transferencia + +trx_applicationmodel.aspect.trx_transferRelated.title=Relacionado con transferencia +trx_applicationmodel.aspect.trx_transferRelated.description=Los nodos con este aspecto est\u00e1n relacionados con una transferencia determinada. + +trx_applicationmodel.aspect.trx_enableable.title=Activable +trx_applicationmodel.aspect.trx_enableable.description=Activable + +trx_applicationmodel.aspect.trx_transferred.name=Transferido +trx_applicationmodel.aspect.trx_transferred.description=Los nodos con este aspecto han sido transferidos de un repositorio a otro + +trx_applicationmodel.aspect.trx_alien.name=Ajeno a transferencia +trx_applicationmodel.aspect.trx_alien.description=Los nodos con este aspecto son nodos ajenos o han sido invadidos por otros nodos ajenos + + diff --git a/config/alfresco/messages/transfer-model_fr.properties b/config/alfresco/messages/transfer-model_fr.properties new file mode 100644 index 0000000000..1dd0cfcaab --- /dev/null +++ b/config/alfresco/messages/transfer-model_fr.properties @@ -0,0 +1,86 @@ +# Display labels for Transfer Model French + +trx_applicationmodel.description=Alfresco Transfer Application Model + +trx_applicationmodel.type.trx_transferGroup.title=Transfer Group +trx_applicationmodel.type.trx_transferGroup.description=The definition of a transfer group + +trx_applicationmodel.type.trx_transferLock.title=Transfer Lock +trx_applicationmodel.type.trx_transferLock.description=Node type used to represent the transfer lock node + +trx_applicationmodel.type.trx_transferRecord.title=Transfer Record +trx_applicationmodel.type.trx_transferRecord.description=Node type used to record transfer information + +trx_applicationmodel.type.trx_tempTransferStore.title=Temp Transfer Store +trx_applicationmodel.type.trx_tempTransferStore.description=Node type used for storage of temporarily orphaned incoming nodes + +trx_applicationmodel.type.trx_transferReport.title=Transfer Report +trx_applicationmodel.type.trx_transferReport.description=Transfer Report + +trx_applicationmodel.type.trx_transferReportDest.title=Transfer Report From Destination +trx_applicationmodel.type.trx_transferReportDest.description=Transfer Report From Destination + +trx_applicationmodel.type.trx_transferTarget.title=Transfer Target +trx_applicationmodel.type.trx_transferTarget.description=The definition of a transfer target + +trx_applicationmodel.property.trx_endpointhost.title=Hte de destination +trx_applicationmodel.property.trx_endpointhost.description=Hte de destination + +trx_applicationmodel.property.trx_endpointport.title=Port de destination +trx_applicationmodel.property.trx_endpointport.description=Port de destination + +trx_applicationmodel.property.trx_endpointpath.title=Chemin de destination +trx_applicationmodel.property.trx_endpointpath.description=Chemin de destination + +trx_applicationmodel.property.trx_endpointprotocol.title=Endpoint Protocol +trx_applicationmodel.property.trx_endpointprotocol.description=Endpoint Protocol + +trx_applicationmodel.property.trx_username.title=Username +trx_applicationmodel.property.trx_username.description=Username + +trx_applicationmodel.property.trx_password.title=Password +trx_applicationmodel.property.trx_password.description=Password + +trx_applicationmodel.property.trx_progressPosition.title=Progress Position +trx_applicationmodel.property.trx_progressPosition.description=Progress Position + +trx_applicationmodel.property.trx_progressEndpoint.title=Progress Endpoint +trx_applicationmodel.property.trx_progressEndpoint.description=Progress Endpoint + +trx_applicationmodel.property.trx_transferStatus.title=Transfer Status +trx_applicationmodel.property.trx_transferStatus.description=Transfer Status + +trx_applicationmodel.property.trx_transferError.title=Transfer Error +trx_applicationmodel.property.trx_transferError.description=Transfer Error + +trx_applicationmodel.property.trx_enabled.title=Enabled +trx_applicationmodel.property.trx_enabled.description=Is this enabled or disabled + +trx_applicationmodel.property.trx_transferId.title=Transfer Id +trx_applicationmodel.property.trx_transferId.description=Transfer Id + +trx_applicationmodel.property.trx_invadedBy.title=Invaded By +trx_applicationmodel.property.trx_invadedBy.description=The repositories that have invaded this node + +trx_applicationmodel.property.trx_repositoryId.title=Source Repository. +trx_applicationmodel.property.trx_repositoryId.description=The repository id that this node originates from. + +trx_applicationmodel.property.trx_fromRepositoryId.title=From Repository Id +trx_applicationmodel.property.trx_fromContent.description=The content URLs transferred with this node + +trx_applicationmodel.association.trx_orphan.title=Transfer Orphan +trx_applicationmodel.association.trx_orphan.description=Transfer Orphan + +trx_applicationmodel.aspect.trx_transferRelated.title=Transfer Related +trx_applicationmodel.aspect.trx_transferRelated.description=Nodes with this aspect are related to a particular transfer. + +trx_applicationmodel.aspect.trx_enableable.title=Enableable +trx_applicationmodel.aspect.trx_enableable.description=Enableable + +trx_applicationmodel.aspect.trx_transferred.name=Transferred +trx_applicationmodel.aspect.trx_transferred.description=Nodes with this aspect have been transferred from one repository to another + +trx_applicationmodel.aspect.trx_alien.name=Transfer Alien +trx_applicationmodel.aspect.trx_alien.description=Nodes with this aspect are either alien nodes or have been invaded by other alien nodes + + diff --git a/config/alfresco/messages/transfer-model_it.properties b/config/alfresco/messages/transfer-model_it.properties new file mode 100755 index 0000000000..295fe080f8 --- /dev/null +++ b/config/alfresco/messages/transfer-model_it.properties @@ -0,0 +1,86 @@ +# Display labels for Transfer Model + +trx_applicationmodel.description=Modello di applicazione trasferimento Alfresco + +trx_applicationmodel.type.trx_transferGroup.title=Gruppo di trasferimento +trx_applicationmodel.type.trx_transferGroup.description=Definizione di un gruppo di trasferimento + +trx_applicationmodel.type.trx_transferLock.title=Blocco di trasferimento +trx_applicationmodel.type.trx_transferLock.description=Tipo di nodo utilizzato per rappresentare il nodo di blocco trasferimento + +trx_applicationmodel.type.trx_transferRecord.title=Record di trasferimento +trx_applicationmodel.type.trx_transferRecord.description=Tipo di nodo utilizzando per registrare le informazioni sul trasferimento + +trx_applicationmodel.type.trx_tempTransferStore.title=Deposito temporaneo di trasferimento +trx_applicationmodel.type.trx_tempTransferStore.description=Tipo di nodo utilizzato per l'archiviazione dei nodi in ingresso temporaneamente orfani + +trx_applicationmodel.type.trx_transferReport.title=Rapporto di trasferimento +trx_applicationmodel.type.trx_transferReport.description=Rapporto di trasferimento + +trx_applicationmodel.type.trx_transferReportDest.title=Rapporto di trasferimento dalla destinazione +trx_applicationmodel.type.trx_transferReportDest.description=Rapporto di trasferimento dalla destinazione + +trx_applicationmodel.type.trx_transferTarget.title=Destinazione trasferimento +trx_applicationmodel.type.trx_transferTarget.description=Definizione di una destinazione di trasferimento + +trx_applicationmodel.property.trx_endpointhost.title=Host endpoint +trx_applicationmodel.property.trx_endpointhost.description=Host endpoint + +trx_applicationmodel.property.trx_endpointport.title=Porta endpoint +trx_applicationmodel.property.trx_endpointport.description=Porta endpoint + +trx_applicationmodel.property.trx_endpointpath.title=Percorso endpoint +trx_applicationmodel.property.trx_endpointpath.description=Percorso endpoint + +trx_applicationmodel.property.trx_endpointprotocol.title=Protocollo endpoint +trx_applicationmodel.property.trx_endpointprotocol.description=Protocollo endpoint + +trx_applicationmodel.property.trx_enabled.title=Abilitato +trx_applicationmodel.property.trx_enabled.description=Pu\u00f2 essere abilitato o disabilitato + +trx_applicationmodel.property.trx_username.title=Nome utente +trx_applicationmodel.property.trx_username.description=Nome utente + +trx_applicationmodel.property.trx_password.title=Password +trx_applicationmodel.property.trx_password.description=Password + +trx_applicationmodel.property.trx_progressPosition.title=Posizione progresso +trx_applicationmodel.property.trx_progressPosition.description=Posizione progresso + +trx_applicationmodel.property.trx_progressEndpoint.title=Endpoint progresso +trx_applicationmodel.property.trx_progressEndpoint.description=Endpoint progresso + +trx_applicationmodel.property.trx_transferStatus.title=Stato di trasferimento +trx_applicationmodel.property.trx_transferStatus.description=Stato di trasferimento + +trx_applicationmodel.property.trx_transferError.title=Errore di trasferimento +trx_applicationmodel.property.trx_transferError.description=Errore di trasferimento + +trx_applicationmodel.property.trx_transferId.title=ID trasferimento +trx_applicationmodel.property.trx_transferId.description=ID trasferimento + +trx_applicationmodel.property.trx_invadedBy.title=Invaso da +trx_applicationmodel.property.trx_invadedBy.description=Repository che hanno invaso il nodo corrente + +trx_applicationmodel.property.trx_repositoryId.title=Repository fonte. +trx_applicationmodel.property.trx_repositoryId.description=ID del repository da cui deriva il nodo corrente. + +trx_applicationmodel.property.trx_fromRepositoryId.title=Da ID repository +trx_applicationmodel.property.trx_fromContent.description=URL di contenuto trasferiti con il nodo corrente + +trx_applicationmodel.association.trx_orphan.title=Trasferimento contenuto orfano +trx_applicationmodel.association.trx_orphan.description=Trasferimento contenuto orfano + +trx_applicationmodel.aspect.trx_transferRelated.title=Trasferimento contenuto correlato +trx_applicationmodel.aspect.trx_transferRelated.description=I nodi con questo aspetto sono correlati a un trasferimento specifico. + +trx_applicationmodel.aspect.trx_enableable.title=Abilitabile +trx_applicationmodel.aspect.trx_enableable.description=Abilitabile + +trx_applicationmodel.aspect.trx_transferred.name=Trasferito +trx_applicationmodel.aspect.trx_transferred.description=I nodi con questo aspetto sono stati trasferiti da un repository all'altro + +trx_applicationmodel.aspect.trx_alien.name=Trasferimento alien +trx_applicationmodel.aspect.trx_alien.description=I nodi con questo aspetto sono nodi alien oppure sono stati invasi da altri nodi alien + + diff --git a/config/alfresco/messages/transfer-model_ja.properties b/config/alfresco/messages/transfer-model_ja.properties new file mode 100755 index 0000000000..d894753e26 --- /dev/null +++ b/config/alfresco/messages/transfer-model_ja.properties @@ -0,0 +1,86 @@ +# Display labels for Transfer Model + +trx_applicationmodel.description=Alfresco\u8ee2\u9001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30fb\u30e2\u30c7\u30eb + +trx_applicationmodel.type.trx_transferGroup.title=\u8ee2\u9001\u30b0\u30eb\u30fc\u30d7 +trx_applicationmodel.type.trx_transferGroup.description=\u8ee2\u9001\u30b0\u30eb\u30fc\u30d7\u306e\u5b9a\u7fa9 + +trx_applicationmodel.type.trx_transferLock.title=\u8ee2\u9001\u30ed\u30c3\u30af +trx_applicationmodel.type.trx_transferLock.description=\u8ee2\u9001\u30ed\u30c3\u30af\u30fb\u30ce\u30fc\u30c9\u3092\u8868\u3059\u306e\u306b\u4f7f\u7528\u3059\u308b\u30ce\u30fc\u30c9\u30fb\u30bf\u30a4\u30d7 + +trx_applicationmodel.type.trx_transferRecord.title=\u8ee2\u9001\u8a18\u9332 +trx_applicationmodel.type.trx_transferRecord.description=\u8ee2\u9001\u60c5\u5831\u3092\u8a18\u9332\u3059\u308b\u306e\u306b\u4f7f\u7528\u3059\u308b\u30ce\u30fc\u30c9\u30fb\u30bf\u30a4\u30d7 + +trx_applicationmodel.type.trx_tempTransferStore.title=\u4e00\u6642\u8ee2\u9001\u30b9\u30c8\u30a2 +trx_applicationmodel.type.trx_tempTransferStore.description=\u4e00\u6642\u7684\u306b\u5b64\u7acb\u3057\u305f\u7740\u4fe1\u30ce\u30fc\u30c9\u306e\u4fdd\u5b58\u306b\u4f7f\u7528\u3059\u308b\u30ce\u30fc\u30c9\u30fb\u30bf\u30a4\u30d7 + +trx_applicationmodel.type.trx_transferReport.title=\u8ee2\u9001\u30ec\u30dd\u30fc\u30c8 +trx_applicationmodel.type.trx_transferReport.description=\u8ee2\u9001\u30ec\u30dd\u30fc\u30c8 + +trx_applicationmodel.type.trx_transferReportDest.title=\u5b9b\u5148\u304b\u3089\u306e\u8ee2\u9001\u30ec\u30dd\u30fc\u30c8 +trx_applicationmodel.type.trx_transferReportDest.description=\u5b9b\u5148\u304b\u3089\u306e\u8ee2\u9001\u30ec\u30dd\u30fc\u30c8 + +trx_applicationmodel.type.trx_transferTarget.title=\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8 +trx_applicationmodel.type.trx_transferTarget.description=\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u5b9a\u7fa9 + +trx_applicationmodel.property.trx_endpointhost.title=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30fb\u30db\u30b9\u30c8 +trx_applicationmodel.property.trx_endpointhost.description=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30fb\u30db\u30b9\u30c8 + +trx_applicationmodel.property.trx_endpointport.title=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30fb\u30dd\u30fc\u30c8 +trx_applicationmodel.property.trx_endpointport.description=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30fb\u30dd\u30fc\u30c8 + +trx_applicationmodel.property.trx_endpointpath.title=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30fb\u30d1\u30b9 +trx_applicationmodel.property.trx_endpointpath.description=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30fb\u30d1\u30b9 + +trx_applicationmodel.property.trx_endpointprotocol.title=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30fb\u30d7\u30ed\u30c8\u30b3\u30eb +trx_applicationmodel.property.trx_endpointprotocol.description=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30fb\u30d7\u30ed\u30c8\u30b3\u30eb + +trx_applicationmodel.property.trx_enabled.title=\u6709\u52b9 +trx_applicationmodel.property.trx_enabled.description=\u6709\u52b9\u304b\u7121\u52b9\u304b + +trx_applicationmodel.property.trx_username.title=\u30e6\u30fc\u30b6\u540d +trx_applicationmodel.property.trx_username.description=\u30e6\u30fc\u30b6\u540d + +trx_applicationmodel.property.trx_password.title=\u30d1\u30b9\u30ef\u30fc\u30c9 +trx_applicationmodel.property.trx_password.description=\u30d1\u30b9\u30ef\u30fc\u30c9 + +trx_applicationmodel.property.trx_progressPosition.title=\u9032\u884c\u4f4d\u7f6e +trx_applicationmodel.property.trx_progressPosition.description=\u9032\u884c\u4f4d\u7f6e + +trx_applicationmodel.property.trx_progressEndpoint.title=\u9032\u6357\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8 +trx_applicationmodel.property.trx_progressEndpoint.description=\u9032\u6357\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8 + +trx_applicationmodel.property.trx_transferStatus.title=\u8ee2\u9001\u30b9\u30c6\u30fc\u30bf\u30b9 +trx_applicationmodel.property.trx_transferStatus.description=\u8ee2\u9001\u30b9\u30c6\u30fc\u30bf\u30b9 + +trx_applicationmodel.property.trx_transferError.title=\u8ee2\u9001\u30a8\u30e9\u30fc +trx_applicationmodel.property.trx_transferError.description=\u8ee2\u9001\u30a8\u30e9\u30fc + +trx_applicationmodel.property.trx_transferId.title=\u8ee2\u9001ID +trx_applicationmodel.property.trx_transferId.description=\u8ee2\u9001ID + +trx_applicationmodel.property.trx_invadedBy.title=\u4fb5\u5165\u8005 +trx_applicationmodel.property.trx_invadedBy.description=\u3053\u306e\u30ce\u30fc\u30c9\u306b\u4fb5\u5165\u3057\u305f\u30ea\u30dd\u30b8\u30c8\u30ea + +trx_applicationmodel.property.trx_repositoryId.title=\u30bd\u30fc\u30b9\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea +trx_applicationmodel.property.trx_repositoryId.description=\u3053\u306e\u30ce\u30fc\u30c9\u306e\u5143\u306b\u306a\u308b\u30ea\u30dd\u30b8\u30c8\u30eaID\u3002 + +trx_applicationmodel.property.trx_fromRepositoryId.title=\u30ea\u30dd\u30b8\u30c8\u30eaID\u304b\u3089 +trx_applicationmodel.property.trx_fromContent.description=\u3053\u306e\u30ce\u30fc\u30c9\u3067\u8ee2\u9001\u3055\u308c\u305f\u30b3\u30f3\u30c6\u30f3\u30c4URL + +trx_applicationmodel.association.trx_orphan.title=\u5b64\u7acb\u884c\u3092\u8ee2\u9001\u3059\u308b +trx_applicationmodel.association.trx_orphan.description=\u5b64\u7acb\u884c\u3092\u8ee2\u9001\u3059\u308b + +trx_applicationmodel.aspect.trx_transferRelated.title=\u8ee2\u9001\u306b\u95a2\u9023 +trx_applicationmodel.aspect.trx_transferRelated.description=\u3053\u306e\u30a2\u30b9\u30da\u30af\u30c8\u306e\u30ce\u30fc\u30c9\u306f\u3001\u7279\u5b9a\u306e\u8ee2\u9001\u306b\u95a2\u9023\u4ed8\u3051\u3089\u308c\u307e\u3059\u3002 + +trx_applicationmodel.aspect.trx_enableable.title=\u6709\u52b9\u5316\u53ef\u80fd +trx_applicationmodel.aspect.trx_enableable.description=\u6709\u52b9\u5316\u53ef\u80fd + +trx_applicationmodel.aspect.trx_transferred.name=\u8ee2\u9001\u6e08\u307f +trx_applicationmodel.aspect.trx_transferred.description=\u3053\u306e\u30a2\u30b9\u30da\u30af\u30c8\u306e\u30ce\u30fc\u30c9\u306f\u3001\u3042\u308b\u30ea\u30dd\u30b8\u30c8\u30ea\u304b\u3089\u5225\u306e\u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u3068\u8ee2\u9001\u3055\u308c\u307e\u3057\u305f\u3002 + +trx_applicationmodel.aspect.trx_alien.name=\u30a8\u30a4\u30ea\u30a2\u30f3\u3092\u8ee2\u9001\u3059\u308b +trx_applicationmodel.aspect.trx_alien.description=\u3053\u306e\u30a2\u30b9\u30da\u30af\u30c8\u306e\u30ce\u30fc\u30c8\u306f\u3001\u30a8\u30a4\u30ea\u30a2\u30f3\u30fb\u30ce\u30fc\u30c9\u3067\u3042\u308b\u304b\u3001\u4ed6\u306e\u30a8\u30a4\u30ea\u30a2\u30f3\u30fb\u30ce\u30fc\u30c9\u306b\u3088\u3063\u3066\u4fb5\u5165\u3055\u308c\u3066\u3044\u307e\u3059\u3002 + + diff --git a/config/alfresco/messages/transfer-service.properties b/config/alfresco/messages/transfer-service.properties index 9bf0508f21..c590ebb491 100644 --- a/config/alfresco/messages/transfer-service.properties +++ b/config/alfresco/messages/transfer-service.properties @@ -8,6 +8,7 @@ transfer_service.target_exists=Unable to create a new transfer target with the n transfer_service.comms.unsupported_protocol=Unsupported protocol: {0} transfer_service.comms.unsuccessful_response=Received unsuccessful response code from target server: {0}, {1} transfer_service.comms.http_request_failed=Failed to execute HTTP request {0} to target: {1} status: {2} +transfer_service.incompatible_versions=Unable to transfer between incompatible versions transferId:{0} from:{1} to:{2} transfer_service.no_nodes=No nodes to transfer transfer_service.target_not_enabled=Transfer target not enabled {0} transfer_service.cancelled=Transfer cancelled diff --git a/config/alfresco/messages/transfer-service_de.properties b/config/alfresco/messages/transfer-service_de.properties index 34b18e378e..6fcc9a430b 100755 --- a/config/alfresco/messages/transfer-service_de.properties +++ b/config/alfresco/messages/transfer-service_de.properties @@ -6,26 +6,27 @@ transfer_service.unable_to_find_transfer_target=Kann \u00dcbertragungsziel mit N transfer_service.unable_to_transfer_async=Kann Async nicht \u00fcbertragen transfer_service.target_exists=Kann kein neues \u00dcbertragungsziel mit Namen {0} erstellen, da unter diesem Namen bereits ein Ziel existiert. transfer_service.comms.unsupported_protocol=Nicht unterst\u00fctztes Protokoll: {0} -transfer_service.comms.unsuccessful_response=Von Zielserver erfolglsoen Antwort Code erhalten: {0}, {1} +transfer_service.comms.unsuccessful_response=Von Zielserver erfolglosen Antwort Code erhalten: {0}, {1} transfer_service.comms.http_request_failed=HTTP Anfrage {0} an Ziel: {1} Status: {2} nicht ausgef\u00fchrt -transfer_service.no_nodes=Keine zu \u00fcbertragenden Nodes +transfer_service.incompatible_versions=\u00dcbertragung zwischen nicht kompatiblen Versionen nicht m\u00f6glich, \u00dcbertragungs-ID:{0} von:{1} nach:{2} +transfer_service.no_nodes=Keine zu \u00fcbertragenden Knoten transfer_service.target_not_enabled=\u00dcbertragungsziel nicht aktiviert {0} transfer_service.cancelled=\u00dcbertragung abgebrochen transfer_service.failed_to_get_transfer_status=Abrufen des \u00dcbertragungsstatus von Ziel {0} fehlgeschlagen transfer_service.target_error=\u00dcbertragungsziel mit {0} fehlgeschlagen transfer_service.unknown_target_error=Unbekannter Fehler transfer_service.receiver.no_primary_parent_supplied=\u00dcbertragung nicht m\u00f6glich, kein Prim\u00e4relternteil angegeben. -transfer_service.receiver.orphans_exist=\u00dcbertragung nicht m\u00f6glich, Waisen vorhanden. +transfer_service.receiver.orphans_exist=\u00dcbertragung nicht m\u00f6glich, Waisen vorhanden transfer_service.receiver.content_file_missing=\u00dcbertragung nicht m\u00f6glich, Inhaltsdateien fehlen. transfer_service.receiver.failed_to_create_staging_folder=Staging-Verzeichnis f\u00fcr \u00dcbertragung {0} kann nicht erstellt werden transfer_service.receiver.lock_folder_not_found=Angegebener Sperr-Ordner ist nicht auffindbar: {0} transfer_service.receiver.temp_folder_not_found=Angegebener tempor\u00e4rer Ordner f\u00fcr \u00dcbertragung {0} ist nicht auffindbar: {1} transfer_service.receiver.lock_unavailable=\u00dcbertragungssperrung wurde f\u00fcr eine andere eingehende \u00dcbertragung beansprucht. Neue \u00dcbertragung kann nicht gestartet werden. transfer_service.receiver.record_folder_not_found=Angegebener Ordner f\u00fcr Protokolle eingehender \u00dcbertragungen wurde nicht gefunden: {0} -transfer_service.receiver.not_lock_owner=Fehlgeschlagener Versuch der Ausf\u00fchrung des \u00dcbertragungsvorgangs Sperrung wird nicht von angegebener \u00dcbertragung gehalten: {0} +transfer_service.receiver.not_lock_owner=Fehlgeschlagener Versuch, eine \u00dcbertragung auszuf\u00fchren. Sperre nicht von angegebener \u00dcbertragung gehalten: {0} transfer_service.receiver.error_ending_transfer=Anforderung zur Beendigung einer \u00dcbertragung ({0}) hat zu einem Fehler gef\u00fchrt. transfer_service.receiver.error_staging_snapshot=Beim Staging der Momentaufnahme einer Datei zur \u00dcbertragung {0} ist ein Fehler aufgetreten -transfer_service.receiver.error_staging_content=Beim Staging einer Inhaltsdatei zur \u00dcbertragung {0} ist ein Fehler aufgetreten Datei-ID ist {1} +transfer_service.receiver.error_staging_content=Beim Staging der Content-Datei zur \u00dcbertragung {0} ist ein Fehler aufgetreten. Feld-ID ist {1} transfer_service.receiver.no_snapshot_received=Eine Anfrage nach Ausf\u00fchrung einer \u00dcbertragung ({0}) ist eingegangen, es ist aber keine passende Momentaufnahmen-Datei eingegangen. transfer_service.receiver.error_committing_transfer=Beim Versuch der Ausf\u00fchrung der \u00dcbertragung {0} ist ein Fehler aufgetreten transfer_service.receiver.transfer_not_found=Konnte kein Protokoll einer \u00dcbertragungsanfrage finden: {0} @@ -36,7 +37,7 @@ transfer_service.receiver.lock_timed_out=\u00dcbertragungssperre, \u00dcbertragu transfer_service.receiver.lock_not_found=\u00dcbertragungssperre nicht gefunden transfer_service.receiver.error_start=\u00dcbertragung kann nicht gestartet werden transfer_service.receiver.error_generating_requisite=\u00dcbertragungsanforderung kann nicht erzeugt werden -transfer_service.receiver.error.transfer_to_self=bertragung an das gleiche Repository nicht zulssig +transfer_service.receiver.error.transfer_to_self=\u00dcbertragung an das gleiche Repository nicht zul\u00e4ssig transfer_service.missing_endpoint_path=F\u00fcr das \u00dcbertragungsziel wurde kein Endpunkt-Pfad angegeben: {0} transfer_service.missing_endpoint_protocol=F\u00fcr das \u00dcbertragungsziel wurde kein Endpunkt-Protokoll angegeben: {0} @@ -44,4 +45,3 @@ transfer_service.missing_endpoint_host=F\u00fcr das \u00dcbertragungsziel wurde transfer_service.missing_endpoint_port=F\u00fcr das \u00dcbertragungsziel wurde kein Endpunkt-Ausgang angegeben: {0} transfer_service.missing_endpoint_username=F\u00fcr das \u00dcbertragungsziel wurde kein Endpunkt-Benutzername angegeben: {0} transfer_service.missing_endpoint_password=F\u00fcr das \u00dcbertragungsziel wurde kein Endpunkt-Passwort angegeben: {0} - diff --git a/config/alfresco/messages/transfer-service_es.properties b/config/alfresco/messages/transfer-service_es.properties index 02ebdb96ec..2f84f6ad24 100755 --- a/config/alfresco/messages/transfer-service_es.properties +++ b/config/alfresco/messages/transfer-service_es.properties @@ -1,17 +1,18 @@ # Transfer service externalised display strings -transfer_service.unable_to_find_transfer_home=No se puede encontrar el inicio de transferencia. {0} +transfer_service.unable_to_find_transfer_home=No se puede encontrar el inicio de transferencia: {0} transfer_service.unable_to_find_transfer_group=No se puede encontrar grupo de transferencia con nombre, {0} transfer_service.unable_to_find_transfer_target=No se puede encontrar destino de transferencia con nombre, {0} -transfer_service.unable_to_transfer_async=No se puede hacer transferencia as\u00edncrona. +transfer_service.unable_to_transfer_async=No se puede hacer transferencia as\u00edncrona transfer_service.target_exists=No se puede crear un nuevo destino de transferencia con el nombre {0}, porque ya existe un destino con ese nombre. transfer_service.comms.unsupported_protocol=Protocolo no soportado: {0} transfer_service.comms.unsuccessful_response=Recibido c\u00f3digo de respuesta sin \u00e9xito desde el servidor de destino: {0}, {1} transfer_service.comms.http_request_failed=No se pudo ejecutar la solicitud HTTP {0} al destino: {1}, estado: {2} -transfer_service.no_nodes=Ning\u00fan nodo para transferir. +transfer_service.incompatible_versions=No se puede transferir entre versiones incompatibles ID de transferencia:{0} de:{1} a:{2} +transfer_service.no_nodes=Ning\u00fan nodo para transferir transfer_service.target_not_enabled=Destino de transferencia no activado {0} transfer_service.cancelled=Transferencia cancelada -transfer_service.failed_to_get_transfer_status=No se ha podido recuperar el estado de transferencia desde el destino {0}. +transfer_service.failed_to_get_transfer_status=No se ha podido recuperar el estado de transferencia desde el destino {0} transfer_service.target_error=Destino de transferencia fallido con {0} transfer_service.unknown_target_error=Error desconocido transfer_service.receiver.no_primary_parent_supplied=No se puede transferir ning\u00fan padre primario suministrado. @@ -25,16 +26,16 @@ transfer_service.receiver.record_folder_not_found=No se encontr\u00f3 la carpeta transfer_service.receiver.not_lock_owner=Intento fallido de llevar a cabo operaci\u00f3n de transferencia. Bloqueo no contenido por la transferencia especificada: {0} transfer_service.receiver.error_ending_transfer=La solicitud para poner fin a una transferencia ({0}) ha dado lugar a un error. transfer_service.receiver.error_staging_snapshot=Ha ocurrido un error al preparar fichero de instant\u00e1nea para la transferencia {0} -transfer_service.receiver.error_staging_content=Ha ocurrido un error al preparar un fichero de contenido para la transferencia {0} ID del fichero es {1}. +transfer_service.receiver.error_staging_content=Ha ocurrido un error al preparar un fichero de contenido para la transferencia {0}. ID del fichero es {1} transfer_service.receiver.no_snapshot_received=Se ha recibido una solicitud para validar la transferencia ({0}), pero no se ha recibido ning\u00fan fichero de instant\u00e1nea coincidente. transfer_service.receiver.error_committing_transfer=Ha ocurrido un error al intentar validad la transferencia {0} transfer_service.receiver.transfer_not_found=No se pudo encontrar ning\u00fan registro de la transferencia solicitada: {0} -transfer_service.receiver.transfer_cancelled=La transferencia ha sido cancelada. {0} +transfer_service.receiver.transfer_cancelled=La transferencia ha sido cancelada: {0} transfer_service.no_encoding=No se puede deserializar el valor, ninguna transformaci\u00f3n para la codificaci\u00f3n de {0} transfer_service.unable_to_deserialise=No se puede deserializar el valor -transfer_service.receiver.lock_timed_out=Bloqueo de transferencia expirado con transferId: {0} +transfer_service.receiver.lock_timed_out=Bloqueo de transferencia expirado con transfer Id: {0} transfer_service.receiver.lock_not_found=No se encontr\u00f3 el bloqueo de transferencia -transfer_service.receiver.error_start=No se puede iniciar una nueva transferencia. +transfer_service.receiver.error_start=No se puede iniciar una nueva transferencia transfer_service.receiver.error_generating_requisite=No se puede iniciar un requisito de transferencia transfer_service.receiver.error.transfer_to_self=No se permite la transferencia al mismo repositorio diff --git a/config/alfresco/messages/transfer-service_fr.properties b/config/alfresco/messages/transfer-service_fr.properties index c17f8f822e..d8ac8d4128 100755 --- a/config/alfresco/messages/transfer-service_fr.properties +++ b/config/alfresco/messages/transfer-service_fr.properties @@ -1,46 +1,47 @@ # Transfer service externalised display strings -transfer_service.unable_to_find_transfer_home=Accueil du transfert introuvable\u00a0: {0} +transfer_service.unable_to_find_transfer_home=Accueil du transfert introuvable : {0} transfer_service.unable_to_find_transfer_group=Groupe de transfert portant le nom {0} introuvable transfer_service.unable_to_find_transfer_target=Cible de transfert portant le nom {0} introuvable transfer_service.unable_to_transfer_async=Impossible de transf\u00e9rer de mani\u00e8re async. transfer_service.target_exists=Impossible de cr\u00e9er une cible de transfert portant le nom {0} car une cible porte d\u00e9j\u00e0 ce nom. -transfer_service.comms.unsupported_protocol=Protocole non pris en charge\u00a0: {0} -transfer_service.comms.unsuccessful_response=Code de r\u00e9ponse infructueuse re\u00e7u depuis le serveur cible\u00a0: {0}, {1} -transfer_service.comms.http_request_failed=\u00c9chec d''ex\u00e9cution de la requ\u00eate\u00a0HTTP {0} vers la cible\u00a0: {1} statut\u00a0: {2} -transfer_service.no_nodes=Aucun n\u009cud \u00e0 transf\u00e9rer +transfer_service.comms.unsupported_protocol=Protocole non pris en charge : {0} +transfer_service.comms.unsuccessful_response=Code de r\u00e9ponse infructueuse re\u00e7u depuis le serveur cible : {0}, {1} +transfer_service.comms.http_request_failed=Echec d''ex\u00e9cution de la requ\u00eate HTTP {0} vers la cible : {1} statut : {2} +transfer_service.incompatible_versions=Transfert impossible entre des versions incompatibles. Identifiant de transfert : {0} de : {1} vers : {2} +transfer_service.no_nodes=Aucun noeud \u00e0 transf\u00e9rer transfer_service.target_not_enabled=Cible de transfert non activ\u00e9e {0} transfer_service.cancelled=Transfert annul\u00e9 -transfer_service.failed_to_get_transfer_status=\u00c9chec de r\u00e9cup\u00e9ration du statut de transfert depuis la cible {0} -transfer_service.target_error=\u00c9chec de la cible de transfert avec {0} +transfer_service.failed_to_get_transfer_status=Echec de r\u00e9cup\u00e9ration du statut de transfert depuis la cible {0} +transfer_service.target_error=Echec de la cible de transfert avec {0} transfer_service.unknown_target_error=Erreur inconnue transfer_service.receiver.no_primary_parent_supplied=Impossible de transf\u00e9rer le parent non primaire fourni. transfer_service.receiver.orphans_exist=Transfert impossible, des orphelins sont pr\u00e9sents transfer_service.receiver.content_file_missing=Transfert impossible, des fichiers de contenu sont manquants. transfer_service.receiver.failed_to_create_staging_folder=Impossible de cr\u00e9er le r\u00e9pertoire d''activation pour le transfert {0} -transfer_service.receiver.lock_folder_not_found=Impossible de rep\u00e9rer l''emplacement du dossier du verrou sp\u00e9cifi\u00e9\u00a0: {0} -transfer_service.receiver.temp_folder_not_found=Impossible de rep\u00e9rer l''emplacement du dossier temporaire sp\u00e9cifi\u00e9 pour le transfert {0}\u00a0: {1} +transfer_service.receiver.lock_folder_not_found=Impossible de rep\u00e9rer l''emplacement du dossier du verrou sp\u00e9cifi\u00e9 : {0} +transfer_service.receiver.temp_folder_not_found=Impossible de rep\u00e9rer l''emplacement du dossier temporaire sp\u00e9cifi\u00e9 pour le transfert {0} : {1} transfer_service.receiver.lock_unavailable=Le verrou de transfert a \u00e9t\u00e9 r\u00e9clam\u00e9 pour un autre transfert entrant. Impossible de d\u00e9marrer un nouveau transfert. -transfer_service.receiver.record_folder_not_found=Dossier sp\u00e9cifi\u00e9 pour contenir les enregistrements de transfert entrant introuvable\u00a0: {0} -transfer_service.receiver.not_lock_owner=\u00c9chec de la tentative d''ex\u00e9cution de l'op\u00e9ration de transfert. Verrou non d\u00e9tenu par le transfert sp\u00e9cifi\u00e9\u00a0: {0} +transfer_service.receiver.record_folder_not_found=Dossier sp\u00e9cifi\u00e9 pour contenir les enregistrements de transfert entrant introuvable : {0} +transfer_service.receiver.not_lock_owner=Echec de la tentative d'ex\u00e9cution de l'op\u00e9ration de transfert. Verrou non d\u00e9tenu par le transfert sp\u00e9cifi\u00e9 : {0} transfer_service.receiver.error_ending_transfer=La requ\u00eate de fin d''un transfert ({0}) a entra\u00een\u00e9 une erreur. -transfer_service.receiver.error_staging_snapshot=Une erreur s''est produite lors de l''activation du fichier de capture d''\u00e9cran pour le transfert {0} -transfer_service.receiver.error_staging_content=Une erreur s''est produite lors de l''activation d'un fichier de contenu pour le transfert {0}. L''identifiant du fichier est {1} -transfer_service.receiver.no_snapshot_received=Une requ\u00eate a \u00e9t\u00e9 re\u00e7ue pour la soumission d'un transfert ({0}) mais aucun fichier de capture d''\u00e9cran correspondant n''a \u00e9t\u00e9 re\u00e7u. +transfer_service.receiver.error_staging_snapshot=Une erreur s''est produite lors de l''activation du fichier de clich\u00e9 pour le transfert {0} +transfer_service.receiver.error_staging_content=Une erreur s''est produite lors de l''activation d''un fichier de contenu pour le transfert {0}. L''identifiant du fichier est {1} +transfer_service.receiver.no_snapshot_received=Une requ\u00eate a \u00e9t\u00e9 re\u00e7ue pour la soumission d''un transfert ({0}) mais aucun fichier de clich\u00e9 correspondant n''a \u00e9t\u00e9 re\u00e7u. transfer_service.receiver.error_committing_transfer=Une erreur s''est produite lors de la tentative de soumission du transfert {0} -transfer_service.receiver.transfer_not_found=Aucun enregistrement du transfert requis n''a \u00e9t\u00e9 trouv\u00e9\u00a0: {0} -transfer_service.receiver.transfer_cancelled=Le transfert a \u00e9t\u00e9 annul\u00e9\u00a0: {0} +transfer_service.receiver.transfer_not_found=Aucun enregistrement du transfert requis n''a \u00e9t\u00e9 trouv\u00e9 : {0} +transfer_service.receiver.transfer_cancelled=Le transfert a \u00e9t\u00e9 annul\u00e9 : {0} transfer_service.no_encoding=Impossible de d\u00e9s\u00e9rialiser la valeur, aucune transformation pour l''encodage {0} transfer_service.unable_to_deserialise=Impossible de d\u00e9s\u00e9rialiser la valeur -transfer_service.receiver.lock_timed_out=Verrou de transfert expir\u00e9. Identifiant de transfert\u00a0: {0} +transfer_service.receiver.lock_timed_out=Verrou de transfert expir\u00e9. Identifiant de transfert : {0} transfer_service.receiver.lock_not_found=Verrou de transfert introuvable transfer_service.receiver.error_start=Impossible de d\u00e9marrer un transfert transfer_service.receiver.error_generating_requisite=Impossible de g\u00e9n\u00e9rer une condition de transfert -transfer_service.receiver.error.transfer_to_self=Transfert vers le mme entrept non autoris +transfer_service.receiver.error.transfer_to_self=Transfert vers le m\u00eame entrep\u00f4t non autoris\u00e9 -transfer_service.missing_endpoint_path=Aucun chemin de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert\u00a0: {0} -transfer_service.missing_endpoint_protocol=Aucun protocole de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert\u00a0: {0} -transfer_service.missing_endpoint_host=Aucun h\u00f4te de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert\u00a0: {0} -transfer_service.missing_endpoint_port=Aucun port de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert\u00a0: {0} -transfer_service.missing_endpoint_username=Aucun nom d''utilisateur de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert\u00a0: {0} -transfer_service.missing_endpoint_password=Aucun mot de passe de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert\u00a0: {0} +transfer_service.missing_endpoint_path=Aucun chemin de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert : {0} +transfer_service.missing_endpoint_protocol=Aucun protocole de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert : {0} +transfer_service.missing_endpoint_host=Aucun h\u00f4te de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert : {0} +transfer_service.missing_endpoint_port=Aucun port de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert : {0} +transfer_service.missing_endpoint_username=Aucun nom d''utilisateur de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert : {0} +transfer_service.missing_endpoint_password=Aucun mot de passe de point d''extr\u00e9mit\u00e9 n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9 pour la cible de transfert : {0} diff --git a/config/alfresco/messages/transfer-service_it.properties b/config/alfresco/messages/transfer-service_it.properties index de51c28072..7a21a85881 100755 --- a/config/alfresco/messages/transfer-service_it.properties +++ b/config/alfresco/messages/transfer-service_it.properties @@ -1,17 +1,18 @@ # Transfer service externalised display strings transfer_service.unable_to_find_transfer_home=Impossibile trovare la homepage di trasferimento: {0} -transfer_service.unable_to_find_transfer_group=Impossibile trovare il gruppo di trasferimento con il nome {0} -transfer_service.unable_to_find_transfer_target=Impossibile trovare la destinazione di trasferimento con il nome {0} +transfer_service.unable_to_find_transfer_group=Impossibile trovare il gruppo di trasferimento con il nome, {0} +transfer_service.unable_to_find_transfer_target=Impossibile trovare la destinazione di trasferimento con il nome, {0} transfer_service.unable_to_transfer_async=Impossibile eseguire il trasferimento in modalit\u00e0 asincrona -transfer_service.target_exists=Impossibile creare una nuova destinazione di trasferimento con il nome {0} perch\u00e9 esiste gi\u00e0 una destinazione con questo nome. +transfer_service.target_exists=Impossibile creare una nuova destinazione di trasferimento con il nome, {0}, perch\u00e9 esiste gi\u00e0 una destinazione con questo nome. transfer_service.comms.unsupported_protocol=Protocollo non supportato: {0} transfer_service.comms.unsuccessful_response=Ricevuto codice di risposta di errore dal server di destinazione: {0}, {1} -transfer_service.comms.http_request_failed=Impossibile eseguire la richiesta HTTP {0} per la destinazione: stato {1}: {2} +transfer_service.comms.http_request_failed=Impossibile eseguire la richiesta HTTP {0} per la destinazione: {1} stato: {2} +transfer_service.incompatible_versions=Impossibile eseguire il trasferimento tra ID trasferimento di versioni incompatibili:{0} da:{1} a:{2} transfer_service.no_nodes=Nessun nodo da trasferire transfer_service.target_not_enabled=Destinazione di trasferimento non abilitata {0} transfer_service.cancelled=Trasferimento annullato -transfer_service.failed_to_get_transfer_status=Impossibile recuperare lo stato di trasferimento dalla destinazione {0}. +transfer_service.failed_to_get_transfer_status=Impossibile recuperare lo stato di trasferimento dalla destinazione {0} transfer_service.target_error=Destinazione di trasferimento non riuscita con {0} transfer_service.unknown_target_error=Errore sconosciuto transfer_service.receiver.no_primary_parent_supplied=Impossibile eseguire il trasferimento perch\u00e9 non \u00e8 stato specificato un genitore principale. @@ -22,10 +23,10 @@ transfer_service.receiver.lock_folder_not_found=Impossibile individuare la carte transfer_service.receiver.temp_folder_not_found=Impossibile individuare la cartella temporanea specificata per il trasferimento {0}: {1} transfer_service.receiver.lock_unavailable=Il blocco di trasferimento \u00e8 stato richiesto per un altro trasferimento in ingresso. Impossibile avviare un nuovo trasferimento. transfer_service.receiver.record_folder_not_found=Impossibile trovare la cartella specificata in cui inserire i record del trasferimento in ingresso: {0} -transfer_service.receiver.not_lock_owner=Tentativo di eseguire l'operazione di trasferimento non riuscito. Il trasferimento specificato non dispone del blocco: {0} +transfer_service.receiver.not_lock_owner=Tentativo di eseguire l''operazione di trasferimento non riuscito. Il trasferimento specificato non dispone del blocco: {0} transfer_service.receiver.error_ending_transfer=La richiesta di terminare un trasferimento ({0}) ha generato un errore. transfer_service.receiver.error_staging_snapshot=Si \u00e8 verificato un errore durante la gestione temporanea del file di istantanea per il trasferimento {0} -transfer_service.receiver.error_staging_content=Si \u00e8 verificato un errore durante la gestione temporanea di un file di contenuto per il trasferimento {0}. L'ID del file \u00e8 {1}. +transfer_service.receiver.error_staging_content=Si \u00e8 verificato un errore durante la gestione temporanea di un file di contenuto per il trasferimento {0}. L''ID del file \u00e8 {1} transfer_service.receiver.no_snapshot_received=\u00c8 stata ricevuta una richiesta di commit di un trasferimento ({0}), ma non \u00e8 stato ricevuto il file di istantanea corrispondente. transfer_service.receiver.error_committing_transfer=Si \u00e8 verificato un errore durante il tentativo di commit del trasferimento {0} transfer_service.receiver.transfer_not_found=Impossibile trovare qualsiasi record del trasferimento richiesto: {0} @@ -36,7 +37,7 @@ transfer_service.receiver.lock_timed_out=Timeout blocco di trasferimento con ID: transfer_service.receiver.lock_not_found=Impossibile trovare il blocco di trasferimento transfer_service.receiver.error_start=Impossibile avviare un trasferimento transfer_service.receiver.error_generating_requisite=Impossibile generare il requisito di trasferimento -transfer_service.receiver.error.transfer_to_self=Non consentito eseguire un trasferimento nello stesso repository +transfer_service.receiver.error.transfer_to_self=Non \u00cb consentito eseguire un trasferimento nello stesso repository transfer_service.missing_endpoint_path=Non \u00e8 stato specificato un percorso di endpoint per la destinazione di trasferimento: {0} transfer_service.missing_endpoint_protocol=Non \u00e8 stato specificato un protocollo di endpoint per la destinazione di trasferimento: {0} diff --git a/config/alfresco/messages/transfer-service_ja.properties b/config/alfresco/messages/transfer-service_ja.properties new file mode 100755 index 0000000000..39f3c52d2e --- /dev/null +++ b/config/alfresco/messages/transfer-service_ja.properties @@ -0,0 +1,47 @@ +# Transfer service externalised display strings + +transfer_service.unable_to_find_transfer_home=\u8ee2\u9001\u30db\u30fc\u30e0\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093: {0} +transfer_service.unable_to_find_transfer_group=\u540d\u524d\u3001{0} \u306e\u4ed8\u3044\u305f\u8ee2\u9001\u30b0\u30eb\u30fc\u30d7\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 +transfer_service.unable_to_find_transfer_target=\u540d\u524d\u3001{0} \u306e\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 +transfer_service.unable_to_transfer_async=\u975e\u540c\u671f\u8ee2\u9001\u304c\u3067\u304d\u307e\u305b\u3093\u3002 +transfer_service.target_exists=\u540d\u524d, {0} \u306e\u4ed8\u3044\u305f\u65b0\u898f\u306e\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3002\u305d\u306e\u540d\u524d\u306e\u30bf\u30fc\u30b2\u30c3\u30c8\u304c\u3059\u3067\u306b\u3042\u308a\u307e\u3059\u3002 +transfer_service.comms.unsupported_protocol=\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u306a\u3044\u30d7\u30ed\u30c8\u30b3\u30eb: {0} +transfer_service.comms.unsuccessful_response=\u30bf\u30fc\u30b2\u30c3\u30c8\u306e\u30b5\u30fc\u30d0\u304b\u3089\u5931\u6557\u5fdc\u7b54\u3092\u53d7\u3051\u53d6\u308a\u307e\u3057\u305f: {0}, {1} +transfer_service.comms.http_request_failed=\u30bf\u30fc\u30b2\u30c3\u30c8: {1} \u30b9\u30c6\u30fc\u30bf\u30b9: {2} \u306b\u5bfe\u3059\u308bHTTP\u30ea\u30af\u30a8\u30b9\u30c8 {0} \u306e\u5b9f\u884c\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +transfer_service.incompatible_versions=\u4e92\u63db\u6027\u306e\u306a\u3044\u30d0\u30fc\u30b8\u30e7\u30f3\u306e\u8ee2\u9001ID\u9593\u3067\u306f\u8ee2\u9001\u3067\u304d\u307e\u305b\u3093:{0}\u3001{1}\u304b\u3089{2} +transfer_service.no_nodes=\u8ee2\u9001\u3059\u3079\u304d\u30ce\u30fc\u30c9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 +transfer_service.target_not_enabled=\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u304c\u6709\u52b9\u3067\u3042\u308a\u307e\u305b\u3093 {0} +transfer_service.cancelled=\u8ee2\u9001\u304c\u30ad\u30e3\u30f3\u30bb\u30eb\u3055\u308c\u307e\u3057\u305f\u3002 +transfer_service.failed_to_get_transfer_status=\u30bf\u30fc\u30b2\u30c3\u30c8 {0}\u304b\u3089\u306e\u8ee2\u9001\u30b9\u30c6\u30fc\u30bf\u30b9\u306e\u53d6\u5f97\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +transfer_service.target_error=\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u306f {0} \u3067\u5931\u6557\u3057\u307e\u3057\u305f\u3002 +transfer_service.unknown_target_error=\u4e0d\u660e\u306a\u30a8\u30e9\u30fc +transfer_service.receiver.no_primary_parent_supplied=\u4e00\u6b21\u89aa\u304c\u63d0\u4f9b\u3055\u308c\u3066\u3044\u306a\u3044\u305f\u3081\u8ee2\u9001\u3067\u304d\u307e\u305b\u3093\u3002 +transfer_service.receiver.orphans_exist=\u8ee2\u9001\u3067\u304d\u307e\u305b\u3093\u3002\u5b64\u7acb\u884c\u304c\u5b58\u5728\u3057\u307e\u3059 +transfer_service.receiver.content_file_missing=\u8ee2\u9001\u3067\u304d\u307e\u305b\u3093\u3002\u30b3\u30f3\u30c6\u30f3\u30c4\u30d5\u30a1\u30a4\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002 +transfer_service.receiver.failed_to_create_staging_folder=\u8ee2\u9001 {0} \u7528\u306e\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u4f5c\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f +transfer_service.receiver.lock_folder_not_found=\u6307\u5b9a\u3057\u305f\u30ed\u30c3\u30af\u30d5\u30a9\u30eb\u30c0\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093: {0} +transfer_service.receiver.temp_folder_not_found=\u8ee2\u9001\u7528\u306b\u6307\u5b9a\u3057\u305f\u4e00\u6642\u30d5\u30a9\u30eb\u30c0\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 {0}: {1} +transfer_service.receiver.lock_unavailable=\u8ee2\u9001\u30ed\u30c3\u30af\u306f\u4ed6\u306e\u30a4\u30f3\u30d0\u30a6\u30f3\u30c9\u8ee2\u9001\u306b\u5bfe\u3057\u3066\u5ba3\u8a00\u3055\u308c\u3066\u3044\u307e\u3059\u3002 \u65b0\u305f\u306b\u8ee2\u9001\u3092\u958b\u59cb\u3067\u304d\u307e\u305b\u3093\u3002 +transfer_service.receiver.record_folder_not_found=\u30a4\u30f3\u30d0\u30a6\u30f3\u30c9\u8ee2\u9001\u8a18\u9332\u3092\u4fdd\u6301\u3059\u308b\u7279\u5b9a\u6e08\u30d5\u30a9\u30eb\u30c0\u306e\u767a\u898b\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0} +transfer_service.receiver.not_lock_owner=\u8ee2\u9001\u30aa\u30da\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u5b9f\u884c\u8a66\u307f\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 \u6307\u5b9a\u3057\u305f\u8ee2\u9001\u306b\u3088\u308a\u30ed\u30c3\u30af\u304c\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3067\u3057\u305f: {0} +transfer_service.receiver.error_ending_transfer=\u8ee2\u9001 ({0}) \u3092\u5b8c\u4e86\u3059\u308b\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u7d50\u679c\u3001\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 +transfer_service.receiver.error_staging_snapshot=\u30b9\u30ca\u30c3\u30d7\u30b7\u30e7\u30c3\u30c8\u30d5\u30a1\u30a4\u30eb\u3092\u8ee2\u9001 {0}\u7528\u306b\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u3059\u308b\u904e\u7a0b\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +transfer_service.receiver.error_staging_content=\u30b3\u30f3\u30c6\u30f3\u30c4\u30d5\u30a1\u30a4\u30eb\u3092\u8ee2\u9001\u7528\u306b\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u3059\u308b\u904e\u7a0b\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f {0}\u3002 \u30d5\u30a1\u30a4\u30eb\u306eID\u306f {1} \u3067\u3059 +transfer_service.receiver.no_snapshot_received=\u8ee2\u9001 ({0})\u3092\u30b3\u30df\u30c3\u30c8\u3059\u308b\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u53d7\u3051\u53d6\u308a\u307e\u3057\u305f\u304c\u3001\u30de\u30c3\u30c1\u3059\u308b\u30b9\u30ca\u30c3\u30d7\u30b7\u30e7\u30c3\u30c8\u30d5\u30a1\u30a4\u30eb\u3092\u53d7\u4fe1\u3057\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +transfer_service.receiver.error_committing_transfer=\u8ee2\u9001 {0} \u306e\u30b3\u30df\u30c3\u30c8\u3092\u8a66\u307f\u3066\u3044\u308b\u9593\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +transfer_service.receiver.transfer_not_found=\u30ea\u30af\u30a8\u30b9\u30c8\u3055\u308c\u305f\u8ee2\u9001\u306e\u8a18\u9332\u306e\u767a\u898b\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0} +transfer_service.receiver.transfer_cancelled=\u8ee2\u9001\u306f\u30ad\u30e3\u30f3\u30bb\u30eb\u3055\u308c\u3066\u3044\u307e\u3059: {0} +transfer_service.no_encoding=\u5024\u3092\u975e\u76f4\u5217\u5316\u3067\u304d\u307e\u305b\u3093\u3002\u30b3\u30fc\u30c9\u5316 {0} \u306e\u5909\u63db\u306f\u884c\u308f\u308c\u307e\u305b\u3093 +transfer_service.unable_to_deserialise=\u5024\u3092\u975e\u76f4\u5217\u5316\u3067\u304d\u307e\u305b\u3093 +transfer_service.receiver.lock_timed_out=\u8ee2\u9001\u30ed\u30c3\u30af\u304c\u8ee2\u9001ID\u3092\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u3057\u307e\u3057\u305f: {0} +transfer_service.receiver.lock_not_found=\u8ee2\u9001\u30ed\u30c3\u30af\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f +transfer_service.receiver.error_start=\u8ee2\u9001\u3092\u958b\u59cb\u3067\u304d\u307e\u305b\u3093 +transfer_service.receiver.error_generating_requisite=\u8ee2\u9001\u306e\u5fc5\u8981\u6761\u4ef6\u3092\u751f\u6210\u3067\u304d\u307e\u305b\u3093 +transfer_service.receiver.error.transfer_to_self=\u8ee2\u9001\u30ed\u30c3\u30af\u304c\u8ee2\u9001ID\u3092\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u3057\u307e\u3057\u305f: {0} + +transfer_service.missing_endpoint_path=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30d1\u30b9\u304c\u6307\u5b9a\u3057\u305f\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u306b\u5bfe\u3057\u3066\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093: {0} +transfer_service.missing_endpoint_protocol=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30d7\u30ed\u30c8\u30b3\u30eb\u304c\u6307\u5b9a\u3057\u305f\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u306b\u5bfe\u3057\u3066\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093: {0} +transfer_service.missing_endpoint_host=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30db\u30b9\u30c8\u304c\u6307\u5b9a\u3057\u305f\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u306b\u5bfe\u3057\u3066\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093: {0} +transfer_service.missing_endpoint_port=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30dd\u30fc\u30c8\u304c\u6307\u5b9a\u3057\u305f\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u306b\u5bfe\u3057\u3066\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093: {0} +transfer_service.missing_endpoint_username=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30e6\u30fc\u30b6\u540d\u304c\u6307\u5b9a\u3057\u305f\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u306b\u5bfe\u3057\u3066\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093: {0} +transfer_service.missing_endpoint_password=\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u6307\u5b9a\u3057\u305f\u8ee2\u9001\u30bf\u30fc\u30b2\u30c3\u30c8\u306b\u5bfe\u3057\u3066\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093: {0} diff --git a/config/alfresco/messages/version-service_ja.properties b/config/alfresco/messages/version-service_ja.properties new file mode 100755 index 0000000000..2f50048e3a --- /dev/null +++ b/config/alfresco/messages/version-service_ja.properties @@ -0,0 +1,20 @@ +# Version service externalised display strings + +version_service.err_restore_exists=The node {0} cannot be restored since it already exists. +version_service.err_not_found=The current version label of the node does not exist in the version history. +version_service.err_unsupported=The current implementation of the version service does not support the creation of branches. +version_service.err_one_preceeding=The current implementation of the version service only supports one preceding version. +version_service.err_restore_no_version=The node {0} cannot be restored since there is no version information available for this node. +version_service.err_revert_mismatch=The version provided to revert to does not come from the nodes version history. + + +version_service.migration.patch.noop=Nothing to do (no version histories found in old version store) + +version_service.migration.patch.complete=Migration completed - migrated {0} (out of {1}) of old version histories (to new version store) in {2} secs (deleteImmediately={3}) +version_service.migration.patch.in_progress=Migration in progress - migrated {0} (out of {1}) of old version histories (to new version store) in {2} secs (deleteImmediately={3}) +version_service.migration.patch.warn.skip1=Skipped migration of {0} (out of {1}) batches of old version histories (migrate incomplete) in {2} secs +version_service.migration.patch.warn.skip2=Skipped migration of {0} old version histories (already migrated) + +version_service.migration.delete.complete=Completed background deletion of {0} (out of {1}) migrated version histories (from old version store) in {2} secs +version_service.migration.delete.warn.skip1=Skipped background deletion of {0} (out of {1}) batches old version histories (delete incomplete) in {2} secs +version_service.migration.delete.warn.skip2=Skipped background deletion of {0} old version histories (not migrated) diff --git a/config/alfresco/messages/wcm-services_fr.properties b/config/alfresco/messages/wcm-services_fr.properties index 386a34a665..5ee7f02cfd 100755 --- a/config/alfresco/messages/wcm-services_fr.properties +++ b/config/alfresco/messages/wcm-services_fr.properties @@ -12,6 +12,6 @@ wcm-undo.description=Annule/R\u00e9tablit les ressources modifi\u00e9es incluses wcm-undo.sandbox-id.display-label=Identifiant du stockage du bac \u00e0 sable wcm-undo.path-list.display-label=Liste des chemins des ressources (relatifs au stockage de bac \u00e0 sable) -wcm-revert-snapshot.title=R\u00e9tablir (recopier) un stockage de bac \u00e0 sable\u00a0WCM vers la capture d'\u00e9cran sp\u00e9cifi\u00e9e +wcm-revert-snapshot.title=R\u00e9tablir (recopier) un stockage de bac \u00e0 sable\u00a0WCM vers le clich\u00e9 sp\u00e9cifi\u00e9 wcm-revert-snapshot.description=R\u00e9tablit toutes les ressources dans un bac \u00e0 sable\u00a0WCM vers la version de clich\u00e9 donn\u00e9e wcm-revert-snapshot.version.display-label=Version de clich\u00e9 vers laquelle le r\u00e9tablissement est effectu\u00e9 diff --git a/config/alfresco/messages/wcm-services_it.properties b/config/alfresco/messages/wcm-services_it.properties index 5a9315a9d9..e6af0d957c 100755 --- a/config/alfresco/messages/wcm-services_it.properties +++ b/config/alfresco/messages/wcm-services_it.properties @@ -4,12 +4,12 @@ wcm-submit.title=Invia risorse cambiate dalla sandbox di un autore WCM nella sandbox temporanea corrispondente wcm-submit.description=Invia le risorse cambiate incluse nell'elenco o tutte le risorse cambiate (se l'elenco \u00e8 null/vuoto) -wcm-submit.sandbox-id.display-label=ID del deposito della sandbox +wcm-submit.sandbox-id.display-label=Id del deposito della sandbox wcm-submit.path-list.display-label=Elenco di percorsi delle risorse (rispetto al deposito della sandbox) wcm-undo.title=Annulla risorse cambiate in una sandbox WCM wcm-undo.description=Annulla/inverte le risorse cambiate incluse nell'elenco o tutte le risorse cambiate (se l'elenco \u00e8 null/vuoto) -wcm-undo.sandbox-id.display-label=ID del deposito della sandbox +wcm-undo.sandbox-id.display-label=Id del deposito della sandbox wcm-undo.path-list.display-label=Elenco di percorsi delle risorse (rispetto al deposito della sandbox) wcm-revert-snapshot.title=Inverti (copia avanti) il deposito di una sandbox WCM nell'istantanea specificata diff --git a/config/alfresco/messages/wcm-services_ja.properties b/config/alfresco/messages/wcm-services_ja.properties new file mode 100755 index 0000000000..2a2b75fe9b --- /dev/null +++ b/config/alfresco/messages/wcm-services_ja.properties @@ -0,0 +1,17 @@ +# WCM service related messages + +# WCM actions + +wcm-submit.title=WCM\u306e\u4f5c\u6210\u8005\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u304b\u3089\u5bfe\u5fdc\u3059\u308b\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u30fb\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u3078\u5909\u66f4\u3055\u308c\u305f\u30a2\u30bb\u30c3\u30c8\u306e\u9001\u4fe1 +wcm-submit.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30ea\u30b9\u30c8\u306b\u542b\u307e\u308c\u308b\u5909\u66f4\u6e08\u30a2\u30bb\u30c3\u30c8\u307e\u305f\u306f\u5168\u3066\u306e\u5909\u66f4\u6e08\u30a2\u30bb\u30c3\u30c8\u304c\u9001\u4fe1\u3055\u308c\u307e\u3059\uff08\u30ea\u30b9\u30c8\u304c\u30cc\u30eb/\u30a8\u30f3\u30d7\u30c6\u30a3\u306e\u5834\u5408\uff09 +wcm-submit.sandbox-id.display-label=\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u30fb\u30b9\u30c8\u30a2ID +wcm-submit.path-list.display-label=\u30a2\u30bb\u30c3\u30c8\u30d1\u30b9\u306e\u30ea\u30b9\u30c8\uff08\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u30fb\u30b9\u30c8\u30a2\u3068\u76f8\u5bfe\u7684\uff09 + +wcm-undo.title=WCM\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u306e\u5909\u66f4\u6e08\u30a2\u30bb\u30c3\u30c8\u3092\u5143\u306b\u623b\u3059 +wcm-undo.description=\u3053\u308c\u306b\u3088\u308a\u3001\u30ea\u30b9\u30c8\u306b\u542b\u307e\u308c\u308b\u5909\u66f4\u6e08\u30a2\u30bb\u30c3\u30c8\u307e\u305f\u306f\u5168\u3066\u306e\u5909\u66f4\u6e08\u30a2\u30bb\u30c3\u30c8\u304c\u5143\u306b\u623b\u3055\u308c/\u30ea\u30d0\u30fc\u30c8\u3057\u307e\u3059\uff08\u30ea\u30b9\u30c8\u304c\u30cc\u30eb/\u30a8\u30f3\u30d7\u30c6\u30a3\u306e\u5834\u5408\uff09 +wcm-undo.sandbox-id.display-label=\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u30fb\u30b9\u30c8\u30a2ID +wcm-undo.path-list.display-label=\u30a2\u30bb\u30c3\u30c8\u30d1\u30b9\u306e\u30ea\u30b9\u30c8\uff08\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u30fb\u30b9\u30c8\u30a2\u3068\u76f8\u5bfe\u7684\uff09 + +wcm-revert-snapshot.title=WCM\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u30fb\u30b9\u30c8\u30a2\u3092\u7279\u5b9a\u306e\u30b9\u30ca\u30c3\u30d7\u30b7\u30e7\u30c3\u30c8\u306b\u30ea\u30d0\u30fc\u30c8\u3059\u308b +wcm-revert-snapshot.description=\u3053\u308c\u306b\u3088\u308a\u3001WCM\u30b9\u30ca\u30c3\u30d7\u30b7\u30e7\u30c3\u30c8\u30b9\u30c8\u30a2\u306e\u5168\u3066\u306e\u30a2\u30bb\u30c3\u30c8\u304c\u7279\u5b9a\u306e\u30b9\u30ca\u30c3\u30d7\u30b7\u30e7\u30c3\u30c8\u30d0\u30fc\u30b8\u30e7\u30f3\u306b\u30ea\u30d0\u30fc\u30c8\u3055\u308c\u307e\u3059 +wcm-revert-snapshot.version.display-label=\u30ea\u30d0\u30fc\u30c8\u3059\u308b\u30b9\u30ca\u30c3\u30d7\u30b7\u30e7\u30c3\u30c8\u30d0\u30fc\u30b8\u30e7\u30f3 diff --git a/config/alfresco/messages/webdav-messages_it.properties b/config/alfresco/messages/webdav-messages_it.properties index 095965d08c..1e3f6fc64c 100755 --- a/config/alfresco/messages/webdav-messages_it.properties +++ b/config/alfresco/messages/webdav-messages_it.properties @@ -1,6 +1,6 @@ # webdav HTML page messages -webdav.repository_title=Repository di contenuto Alfresco +webdav.repository_title=Repository di Contenuto Alfresco webdav.directory_listing=Elenco di directory per webdav.column.name=Nome webdav.column.size=Dimensioni diff --git a/config/alfresco/messages/webdav-messages_ja.properties b/config/alfresco/messages/webdav-messages_ja.properties new file mode 100755 index 0000000000..0bf6f1a74d --- /dev/null +++ b/config/alfresco/messages/webdav-messages_ja.properties @@ -0,0 +1,13 @@ +# webdav HTML page messages + +webdav.repository_title=Alfresco\u30b3\u30f3\u30c6\u30f3\u30c4\u30fb\u30ea\u30dd\u30b8\u30c8\u30ea +webdav.directory_listing=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fb\u30ea\u30b9\u30c8 +webdav.column.name=\u540d\u524d +webdav.column.size=\u30b5\u30a4\u30ba +webdav.column.type=\u30bf\u30a4\u30d7 +webdav.column.modifieddate=\u5909\u66f4\u65e5 +webdav.column.navigate_up=\u30ec\u30d9\u30eb\u3092\u4e0a\u3052\u308b +webdav.err.dir=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30fb\u30ea\u30b9\u30c8\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30b7\u30b9\u30c6\u30e0\u7ba1\u7406\u8005\u306b\u554f\u3044\u5408\u308f\u305b\u3066\u304f\u3060\u3055\u3044\u3002 +webdav.size.bytes=\u30d0\u30a4\u30c8 +webdav.size.kilobytes=KB +webdav.size.megabytes=MB diff --git a/config/alfresco/messages/workflow-interpreter-help_ja.properties b/config/alfresco/messages/workflow-interpreter-help_ja.properties new file mode 100755 index 0000000000..398b1b772b --- /dev/null +++ b/config/alfresco/messages/workflow-interpreter-help_ja.properties @@ -0,0 +1 @@ +workflow_console.help=alfresco/messages/workflow-interpreter-help.txt diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index ee1bc62a35..c7ac00137e 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -263,16 +263,28 @@ d:text - + + d:boolean + + + + + + d:long + true + + d:long true true + d:long true + @@ -465,6 +477,25 @@ + + + + Failed Thumbnail + cm:cmobject + false + + + Count of thumbnail failures + d:int + 0 + + + Failed Thumbnail Time + d:datetime + + + + @@ -473,7 +504,7 @@ Thumbnail cm:content - true + false false @@ -572,6 +603,11 @@ + + Person Disabled + Indicates that a cm:person type has been disabled. + + Transformable @@ -997,6 +1033,8 @@ + + Rateable @@ -1014,6 +1052,58 @@ + + + + Likes rating scheme rollups + + + Likes Rating Scheme ratings count + d:int + + true + true + false + + + + Likes Rating Scheme ratings total + d:float + + true + true + false + + + + + + + Five star rating scheme rollups + + + Five Star Rating Scheme ratings count + d:int + + true + true + false + + + + Five Star Rating Scheme ratings total + d:float + + true + true + false + + + + + Attachable @@ -1205,6 +1295,23 @@ + + Failed Thumbnail Source + + + + false + false + + + cm:failedThumbnail + false + true + + + + + diff --git a/config/alfresco/model/systemModel.xml b/config/alfresco/model/systemModel.xml index e19043e250..f53335bfc4 100644 --- a/config/alfresco/model/systemModel.xml +++ b/config/alfresco/model/systemModel.xml @@ -16,6 +16,11 @@ + + + Deleted + Placeholder type for deleted nodes + Base @@ -47,6 +52,9 @@ d:text + + d:text + d:text diff --git a/config/alfresco/model/transferModel.xml b/config/alfresco/model/transferModel.xml index eed3b51986..8647b5ed03 100644 --- a/config/alfresco/model/transferModel.xml +++ b/config/alfresco/model/transferModel.xml @@ -187,7 +187,8 @@ - Transfer Identifier + Transfer Id + Transfer Identifier to uniquely identify a particular transfer d:text true @@ -225,7 +226,8 @@ Nodes with this aspect are either alien nodes or have been invaded by other alien nodes - The repositories that have invaded this node + Invaded By + The repositories that have invaded this node d:text false true diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 8d56a5b036..96818dba05 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1723,6 +1723,7 @@ + @@ -1833,20 +1834,9 @@ 0 3004 3005 - - - - - - - ${spaces.store} - - - - @@ -2087,6 +2077,17 @@ + + patch.db-V3.4-authority-unique-idx + patch.schemaUpgradeScript.description + 0 + 4099 + 4100 + + classpath:alfresco/dbscripts/upgrade/3.4/${db.script.dialect}/authority-unique-idx.sql + + + patch.fixAuthoritiesCrcValues patch.fixAuthoritiesCrcValues.description @@ -2194,6 +2195,21 @@ classpath:alfresco/dbscripts/upgrade/3.4/${db.script.dialect}/property-unique-ctx-value.sql + + patch.db-V3.4-property-unique-ctx-idx + patch.schemaUpgradeScript.description + 0 + 4104 + 4105 + + classpath:alfresco/dbscripts/upgrade/3.4/${db.script.dialect}/property-unique-ctx-idx.sql + + + + + + + patch.db-V3.4-child-assoc-indexes @@ -2273,7 +2289,7 @@ - + @@ -2488,16 +2504,26 @@ - + + + + + + + + + + - + @@ -2544,7 +2570,6 @@ - @@ -2555,5 +2580,183 @@ - + + + patch.db-V3.4-AVM-rename-dupes + patch.schemaUpgradeScript.description + 0 + 4200 + 4201 + + classpath:alfresco/dbscripts/upgrade/3.4/${db.script.dialect}/AVM-rename-dupes.sql + + + + + + + + + + + patch.activitiesEmailTemplate + patch.activitiesEmailTemplate.description + 0 + 4300 + 4301 + + + + + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}/${spaces.templates.email.activities.childname} + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname} + alfresco/templates/activities-email-templates.acp + alfresco/messages/bootstrap-spaces + + + + + + patch.newUserEmailTemplates + patch.newUserEmailTemplates.description + 0 + 4301 + 4302 + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}/${spaces.templates.email.invite.childname} + alfresco/templates/new-user-templates.acp + alfresco/messages/bootstrap-spaces + + + + + + patch.inviteEmailTemplates + patch.inviteEmailTemplates.description + 0 + 4301 + 4302 + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}/${spaces.templates.email.invite.childname} + alfresco/templates/invite-email-templates.acp + alfresco/messages/bootstrap-spaces + + + + + + patch.htmlNotificationMailTemplates + patch.htmlNotificationMailTemplates.description + 0 + 4301 + 4302 + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}/${spaces.templates.email.notify.childname} + alfresco/templates/email_templates2.acp + alfresco/messages/bootstrap-spaces + + + + + + patch.imapSpacesLocaleTemplates + patch.imapSpacesLocaleTemplates.description + 0 + 4302 + 4303 + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.imapConfig.childname}/${spaces.imap_templates.childname} + alfresco/bootstrap/imapSpacesLocales.acp + alfresco/messages/bootstrap-spaces + + + + + + + patch.siteLoadPatch.swsdp + patch.siteLoadPatch.description + 0 + ${version.schema} + 10000 + + + + + + + swsdp + + + + + + alfresco/bootstrap/team-sample-sites/swsdp/AVM.zip + + + + + alfresco/bootstrap/team-sample-sites/swsdp/Users.acp + + + + + alfresco/bootstrap/team-sample-sites/swsdp/People.acp + + + + + alfresco/bootstrap/team-sample-sites/swsdp/Groups.txt + + + + + alfresco/bootstrap/team-sample-sites/swsdp/Contents.acp + + + + + + + + patch.exampleJavaScript + patch.exampleJavaScript.description + 0 + ${version.schema} + 10000 + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.scripts.childname} + alfresco/bootstrap/example_javascripts2.acp + alfresco/messages/bootstrap-spaces + + + diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index 255505c7e1..7d80d56b5f 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -415,10 +415,12 @@ org.alfresco.service.cmr.model.FileFolderService.listFiles=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.model.FileFolderService.listFolders=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.model.FileFolderService.listDeepFolders=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.ReadProperties + org.alfresco.service.cmr.model.FileFolderService.getLocalizedSibling=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.model.FileFolderService.search=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.Read org.alfresco.service.cmr.model.FileFolderService.searchSimple=ACL_NODE.0.sys:base.ReadChildren,AFTER_ACL_NODE.sys:base.Read org.alfresco.service.cmr.model.FileFolderService.rename=ACL_NODE.0.sys:base.WriteProperties org.alfresco.service.cmr.model.FileFolderService.move=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.1.sys:base.CreateChildren + org.alfresco.service.cmr.model.FileFolderService.moveFrom=ACL_NODE.0.sys:base.DeleteNode,ACL_NODE.2.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.copy=ACL_NODE.0.sys:base.ReadProperties,ACL_NODE.1.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.create=ACL_NODE.0.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.delete=ACL_NODE.0.sys:base.DeleteNode @@ -720,6 +722,7 @@ org.alfresco.service.cmr.security.AuthorityService.getAllAuthorities=ACL_ALLOW org.alfresco.service.cmr.security.AuthorityService.findAuthorities=ACL_ALLOW org.alfresco.service.cmr.security.AuthorityService.getAllRootAuthorities=ACL_ALLOW + org.alfresco.service.cmr.security.AuthorityService.getAuthorityNodeRef=ACL_ALLOW org.alfresco.service.cmr.security.AuthorityService.createAuthority=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.service.cmr.security.AuthorityService.addAuthority=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.service.cmr.security.AuthorityService.removeAuthority=ACL_METHOD.ROLE_ADMINISTRATOR @@ -821,6 +824,7 @@ org.alfresco.service.cmr.security.PersonService.isMutable=ACL_ALLOW org.alfresco.service.cmr.security.PersonService.createPerson=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.service.cmr.security.PersonService.deletePerson=ACL_METHOD.ROLE_ADMINISTRATOR + org.alfresco.service.cmr.security.PersonService.notifyPerson=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.service.cmr.security.PersonService.getAllPeople=ACL_ALLOW org.alfresco.service.cmr.security.PersonService.getPeopleFilteredByProperty=ACL_ALLOW org.alfresco.service.cmr.security.PersonService.getPeopleContainer=ACL_ALLOW @@ -878,7 +882,18 @@ - + + + + + + + org.alfresco.service.cmr.admin.RepoAdminService.getRestrictions=ACL_ALLOW + org.alfresco.service.cmr.admin.RepoAdminService.getUsageStatus=ACL_ALLOW + org.alfresco.service.cmr.admin.RepoAdminService.*=ACL_METHOD.ROLE_ADMINISTRATOR + + + diff --git a/config/alfresco/rating-services-context.xml b/config/alfresco/rating-services-context.xml index 5a364e4188..57c974e2ea 100644 --- a/config/alfresco/rating-services-context.xml +++ b/config/alfresco/rating-services-context.xml @@ -44,7 +44,9 @@ + + @@ -53,20 +55,58 @@ + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + @@ -160,11 +160,20 @@ - + + + + + + + + + + {http://www.alfresco.org/model/content/1.0}folder + diff --git a/config/alfresco/repo-admin-context.xml b/config/alfresco/repo-admin-context.xml index 2bc29b3671..e0c12ac7b2 100644 --- a/config/alfresco/repo-admin-context.xml +++ b/config/alfresco/repo-admin-context.xml @@ -4,40 +4,33 @@ - - - - - - + + + + - - - - alfresco.messages.repoadmin-interpreter-help - diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 85616e7529..234d9d21ff 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -20,6 +20,20 @@ dir.indexes.backup=${dir.root}/backup-lucene-indexes # The location for lucene index locks dir.indexes.lock=${dir.indexes}/locks +#Directory to find external license +dir.license.external=. +# Spring resource location of external license files +location.license.external=file://${dir.license.external}/*.lic +# Spring resource location of embedded license files +location.license.embedded=/WEB-INF/alfresco/license/*.lic +# Spring resource location of license files on shared classpath +location.license.shared=classpath*:/alfresco/extension/license/*.lic + +# WebDAV initialization properties +system.webdav.servlet.enabled=true +system.webdav.storeName=${protocols.storeName} +system.webdav.rootPath=${protocols.rootPath} + # Is the JBPM Deploy Process Servlet enabled? # Default is false. Should not be enabled in production environments as the # servlet allows unauthenticated deployment of new workflows. @@ -353,6 +367,7 @@ spaces.content_forms.childname=app:forms spaces.user_homes.childname=app:user_homes spaces.sites.childname=st:sites spaces.templates.email.invite.childname=cm:invite +spaces.templates.email.activities.childname=cm:activities spaces.rendition.rendering_actions.childname=app:rendering_actions spaces.replication.replication_actions.childname=app:replication_actions spaces.wcm_deployed.childname=cm:wcm_deployed @@ -412,6 +427,8 @@ xforms.formatCaption=true # ECM content usages/quotas system.usages.enabled=true +system.usages.clearBatchSize=50 +system.usages.updateBatchSize=50 # Repository endpoint - used by Activity Service repo.remote.url=http://localhost:8080/alfresco @@ -465,6 +482,19 @@ img.dyn=${img.root}/lib img.exe=${img.root}/bin/convert swf.exe=./bin/pdf2swf +# Configuration for handling of failing thumbnails. +# See NodeEligibleForRethumbnailingEvaluator's javadoc for details. +# +# Retry periods limit the frequency with which the repository will attempt to create Share thumbnails +# for content nodes which have previously failed in their thumbnail attempts. +# These periods are in seconds. +# +# 604800s = 60s * 60m * 24h * 7d = 1 week +system.thumbnail.retryPeriod=60 +system.thumbnail.retryCount=2 +system.thumbnail.quietPeriod=604800 +system.thumbnail.quietPeriodRetriesEnabled=true + # Property to enable upgrade from 2.1-A V2.1-A.fixes.to.schema=0 #V2.1-A.fixes.to.schema=82 @@ -491,6 +521,10 @@ nfs.user.mappings=admin nfs.user.mappings.default.uid=0 nfs.user.mappings.default.gid=0 +# Default root path for protocols +protocols.storeName=${spaces.store} +protocols.rootPath=/${spaces.company_home.childname} + # IMAP imap.server.enabled=false imap.server.port=143 @@ -504,13 +538,17 @@ imap.config.server.mountPoints=AlfrescoIMAP imap.config.server.mountPoints.default.mountPointName=IMAP imap.config.server.mountPoints.default.modeName=ARCHIVE imap.config.server.mountPoints.default.store=${spaces.store} -imap.config.server.mountPoints.default.rootPath=/${spaces.company_home.childname} +imap.config.server.mountPoints.default.rootPath=${protocols.rootPath} imap.config.server.mountPoints.value.AlfrescoIMAP.mountPointName=Alfresco IMAP imap.config.server.mountPoints.value.AlfrescoIMAP.modeName=MIXED -# Activity feed max size and max age (eg. 44640 mins = 31 days) +# Activities Feed - refer to subsystem + +# Feed max size (number of entries) activities.feed.max.size=100 -activities.feed.max.age.mins=44640 +# Feed max age (eg. 44640 mins => 31 days) +activities.feed.max.ageMins=44640 + # Subsystem unit test values. Will not have any effect on production servers subsystems.test.beanProp.default.longProperty=123456789123456789 diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index c2f6371fee..6286ef1e32 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -294,122 +294,10 @@ - - - - - org.alfresco.repo.activities.feed.cleanup.FeedCleanupJob - - - - - - - - - - - - - - - - - - 5 - - - 10 - - + - - - org.alfresco.repo.activities.feed.FeedGeneratorJob - - - - - - - - - - - - - - - - - - 0 - - - 30000 - - - - - - - - org.alfresco.repo.activities.post.lookup.PostLookupJob - - - - - - - - - - - - - - - - - - 1 - - - 15000 - - - - - - - - org.alfresco.repo.activities.post.cleanup.PostCleanupJob - - - - - - - - - - - - - - - - - - 10 - - - 10 - - - org.alfresco.repo.admin.patch.impl.MigrateVersionStorePatch$MigrateVersionStoreJob diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml new file mode 100644 index 0000000000..a1478b7411 --- /dev/null +++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${activities.feed.max.ageMins} + + + ${activities.feed.max.size} + + + + + + + + 30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + alfresco/extension/templates/activities + alfresco/templates/activities + + + + + + + + + + + + + + + + + + + + + + + + admin@alfresco.com + + + + + + + + /app:company_home/app:dictionary/app:email_templates/cm:activities/cm:activities-email.ftl + + + + + + + alfresco.messages.activities-service + alfresco.messages.activity-list + alfresco.messages.slingshot + + + + + diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-jobs-context.xml b/config/alfresco/subsystems/ActivitiesFeed/default/activities-jobs-context.xml new file mode 100644 index 0000000000..18e7faca10 --- /dev/null +++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-jobs-context.xml @@ -0,0 +1,161 @@ + + + + + + + + + + org.alfresco.repo.activities.feed.cleanup.FeedCleanupJob + + + + + + + + + + + + + + + + + + + ${activities.feed.cleaner.startDelayMins} + + + ${activities.feed.cleaner.repeatIntervalMins} + + + + + + + + org.alfresco.repo.activities.feed.FeedGeneratorJob + + + + + + + + + + + + + + + + + + + ${activities.feed.generator.startDelayMins} + + + ${activities.feed.generator.repeatIntervalMillis} + + + + + + + + org.alfresco.repo.activities.post.lookup.PostLookupJob + + + + + + + + + + + + + + + + + + ${activities.post.lookup.startDelayMins} + + + ${activities.post.lookup.repeatIntervalMillis} + + + + + + + + org.alfresco.repo.activities.post.cleanup.PostCleanupJob + + + + + + + + + + + + + + + + + + + ${activities.post.cleaner.startDelayMins} + + + ${activities.post.cleaner.repeatIntervalMins} + + + + + + + + org.alfresco.repo.activities.feed.FeedNotifierJob + + + + + + + + + + + + + + + + + + + + + + ${activities.feed.notifier.startDelayMins} + + + ${activities.feed.notifier.repeatIntervalMins} + + + ${activities.feed.notifier.enabled} + + + + diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-jobs.properties b/config/alfresco/subsystems/ActivitiesFeed/default/activities-jobs.properties new file mode 100644 index 0000000000..5b811f8fcf --- /dev/null +++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-jobs.properties @@ -0,0 +1,30 @@ + +# Feed max size (number of entries) +activities.feed.max.size=100 +# Feed max age (eg. 44640 mins => 31 days) +activities.feed.max.ageMins=44640 + +# activities feed email notifier +# please note: +# - refer to "email/OutboundSMTP" subsystem for SMTP properties (mail.*) +# - refer to "sysAdmin" subsystem for Share url/ctx properties (share.*) +activities.feed.notifier.startDelayMins=0 +# Feed notification period (eg. 1440 mins => every 24 hours) +activities.feed.notifier.repeatIntervalMins=1 +activities.feed.notifier.enabled=true + +# activities feed generator +activities.feed.generator.startDelayMins=0 +activities.feed.generator.repeatIntervalMillis=30000 + +# activities feed cleaner +activities.feed.cleaner.startDelayMins=5 +activities.feed.cleaner.repeatIntervalMins=10 + +# activities post cleaner +activities.post.cleaner.startDelayMins=10 +activities.post.cleaner.repeatIntervalMins=10 + +# activities post lookup +activities.post.lookup.startDelayMins=1 +activities.post.lookup.repeatIntervalMillis=15000 \ No newline at end of file diff --git a/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml b/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml index b43c509bfd..e317dce161 100644 --- a/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml +++ b/config/alfresco/subsystems/Authentication/alfrescoNtlm/alfresco-authentication-context.xml @@ -95,6 +95,9 @@ + + + diff --git a/config/alfresco/subsystems/Replication/default/replication-context.xml b/config/alfresco/subsystems/Replication/default/replication-context.xml index 5056a4a369..bb51424c5b 100644 --- a/config/alfresco/subsystems/Replication/default/replication-context.xml +++ b/config/alfresco/subsystems/Replication/default/replication-context.xml @@ -7,6 +7,9 @@ ${replication.transfer.readonly} + + ${replication.enabled} + diff --git a/config/alfresco/subsystems/Replication/default/replication.properties b/config/alfresco/subsystems/Replication/default/replication.properties index 2609979fc7..dc6bee2576 100644 --- a/config/alfresco/subsystems/Replication/default/replication.properties +++ b/config/alfresco/subsystems/Replication/default/replication.properties @@ -1 +1,2 @@ replication.transfer.readonly=true +replication.enabled=false diff --git a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties index e7f9482257..b8db5e3354 100755 --- a/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties +++ b/config/alfresco/subsystems/email/OutboundSMTP/outboundSMTP.properties @@ -1,4 +1,4 @@ -mail.host=smtp.alfresco.com +mail.host=smtp.example.com mail.port=25 mail.username=anonymous mail.password= diff --git a/config/alfresco/subsystems/fileServers/default/file-servers-context.xml b/config/alfresco/subsystems/fileServers/default/file-servers-context.xml index c8bdf85dd7..bbe0f43747 100644 --- a/config/alfresco/subsystems/fileServers/default/file-servers-context.xml +++ b/config/alfresco/subsystems/fileServers/default/file-servers-context.xml @@ -342,10 +342,13 @@ ${filesystem.name} - ${spaces.store} + ${protocols.storeName} - /${spaces.company_home.childname} + ${filesystem.rootPath} + + + ${filesystem.renameShufflePattern} diff --git a/config/alfresco/subsystems/fileServers/default/file-servers.properties b/config/alfresco/subsystems/fileServers/default/file-servers.properties index af166bf5a0..9c2ed117aa 100644 --- a/config/alfresco/subsystems/fileServers/default/file-servers.properties +++ b/config/alfresco/subsystems/fileServers/default/file-servers.properties @@ -1,6 +1,14 @@ filesystem.name=Alfresco filesystem.acl.global.defaultAccessLevel= +### Root directory to open onto ### +filesystem.storeName=${spaces.store} +filesystem.rootPath=${protocols.rootPath} + +# ALF-3856 +# File name patterns that trigger rename shuffle detection +filesystem.renameShufflePattern=(.*\\.tmp)|(.*\\.wbk)|(.*\\.bak)|(.*\\~) + ### CIFS Server Configuration ### cifs.enabled=true cifs.serverName=${localname}A diff --git a/config/alfresco/subsystems/imap/default/imap-server-context.xml b/config/alfresco/subsystems/imap/default/imap-server-context.xml index 2bea07e555..414309a7b5 100644 --- a/config/alfresco/subsystems/imap/default/imap-server-context.xml +++ b/config/alfresco/subsystems/imap/default/imap-server-context.xml @@ -149,6 +149,9 @@ + + + diff --git a/config/alfresco/subsystems/imap/default/imap-server.properties b/config/alfresco/subsystems/imap/default/imap-server.properties index 681138f48b..94b9bded53 100644 --- a/config/alfresco/subsystems/imap/default/imap-server.properties +++ b/config/alfresco/subsystems/imap/default/imap-server.properties @@ -4,6 +4,6 @@ imap.server.host=0.0.0.0 imap.mail.from.default=alfresco@demo.alfresco.org -imap.config.home.store=${spaces.store} -imap.config.home.rootPath=/${spaces.company_home.childname} +imap.config.home.store=${protocols.storeName} +imap.config.home.rootPath=${protocols.rootPath} imap.config.home.folderPath=Imap Home diff --git a/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml index 68d8596dca..93b88a5bf7 100644 --- a/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml +++ b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter-context.xml @@ -11,7 +11,7 @@ ${server.allowedusers} - ${server.transaction.allow-writes} + ${server.allowWrite} ${alfresco.context} diff --git a/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties index fecf5e867d..943136e2bf 100644 --- a/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties +++ b/config/alfresco/subsystems/sysAdmin/default/sysadmin-parameter.properties @@ -1,6 +1,6 @@ server.maxusers=-1 server.allowedusers= -server.transaction.allow-writes=true +server.allowWrite=true alfresco.context=alfresco alfresco.host=${localname} alfresco.port=8080 diff --git a/config/alfresco/template-services-context.xml b/config/alfresco/template-services-context.xml index 82ab907b57..96c9017881 100644 --- a/config/alfresco/template-services-context.xml +++ b/config/alfresco/template-services-context.xml @@ -174,4 +174,4 @@ - \ No newline at end of file + diff --git a/config/alfresco/templates/activities-email-templates.acp b/config/alfresco/templates/activities-email-templates.acp new file mode 100644 index 0000000000000000000000000000000000000000..148cc4a5b622e8737ff6b9c82218f058c2ccdbec GIT binary patch literal 10328 zcmbW71yGxb+O~sRaV_o?4PM;cU5W&EE5+T4ySux)YjJld))p%iEfg>Ehuw2__urmx zXU<9HCCOxXuVFG=@16U3lw_fx5dqJ)k+NWc-=6&I2LXT(U})@Q>0;?*Y5I=Q)Yj0_ zhSAB?*1^Wm$@CpiT@3*Ml`@(suQ#44@9c&IfPlV)005w|z`w2pKI!u#JixywSm3M^ z-{dDW0Kh;10095_q<`lznK{|~1`7C)DIW+v{s!96R<>W`MDsbVF-T?5Vi~XB0MOcu z_xTwP`sLHFel@fR_s@S7hD3!(nKXcO|5;Kco@_XovCOVrUYH?_hp6ZD#BE3UhZdF1 z863L5;Yv(tu4fc05?CLyiaGHTSJlD8e-diQkV5>=D-_M5MsuaQqEQo9#eJHnq~j$D%Pl!QUZY*;4zXfEvq$l9y~IXl75&J$@0o)X zRn%&uV&fdKnq>>m=_wK^Q3l5%jg9W0>V~wwi_@$(L&tYr=fV!X#6*07-W>(K%;_Yf zxVG02A0AzL1R*tL0Z@qJH(PUXQMTGyB3&BV@nrl|=?d&b&MZy02&Y{>_^-Id*R)M+dKJEXCq4`9<;v!*E7M_=)|Q7%MQD6|+SC zlQ-(9U(915RCdPhU!}*=mjyKsGEcnJ@kT`%x*-)N&*y=O2H}z+QsbIdR%aEtY4Ftq zJFU#wX`Mp3$n>X^)QmtS$prlP;ZXpAG)2JyRWNmswxl@^6I*$bOysQXEG3hrDqL+o z-NN2=`GwG5D|V*C-#ms@B0v|Z*^vzkeHp&ZJVxEMKT_7YYRw0!dV;W~2pW}^;Mc%~N1OhiGO@o+MqRyjj5jc7q%6p}lZjP}9X{U#TlMlT4CvB~a z(kFe^>{wdY8A8f~(PCp?cp=LGqaXLApmAE=A4d%mb6mTA>=tUPDhhxM{rJYA`_dGr4-{<Sjo2{wi?5nAA7eG$mbiN0yE9-pU8Zid$NiikODsOF&*3QB=A;naXz6e96)R4Lgs9%&s-^)ClI*s0 z$g0mzHx=^x`U(*_DWM;QvQb(X-}*oqt7`HapAWv~+Rc2mTjPgkTro2jjYyME4kX|S zx#Rz+*P&Oy8EP%;UWzEF=$$tCChevT&+a%vea(n%#$0C?H5)2YCD$+?ajUw9d`TuI!wb=+QCV=H=1Moxm|^q1)IC8sdcJLaJYmikOgn<+`y zZ#kJ)RLEq>0K9>X!7bTrLg@Q zCrlmHf!qDVNwg#}f3(^}F%V5;8$Q(jbRtj<308_xCDj1tdhD`rVE!y_8w-Q&1Q@;+ zvZVupP)x*(O|2h9>q1YgMTw`3i-r~soW&HD7VJa2oSyqRMi9fs6uf^k!REUgVS1+L z6^BCJuR~V`=}RPDmcPtP;?{?Qt+z$%A-f0}=ZqX&za2OXqnt=1n)n_1iXLC$9!Ct> zS$iwcz)7!UTW+|T!{-Fcbr<`mMP;;E9u-&LaCg`DG3)S&#^saJntX(lYmd){I}paS zXWr>Sn0))Y$gEV^wlmXgOzg?`E*VA@I%?TAflqFfA1qSkYT?2l{Yj8&{CPqB%?NM8 z_L%0PhelRS1I!09LISJ_c;1&B|5O$hmsInJp)_BR93IdwaOTQ*WxLxfUKu^=`o^o{ zQ`UFlO@>I77hGb2*=A2;Ui(jz1359Gn5^m=O{P(;iuSVfYI9=-x!(!`0@=1@&fG$$ ztX2y>^aAb*0bOZpmXEhU7->wm>LFKvz=I}ZZVum#Sl|0wCH7z>uyG|`kKpO}AfXK1 zzoI5DL+BbU2+0sS5082&r2!2{wfwd;g5={<%2TBs2z=yS8@3CWA84E>(s$L)JgY^5w+h0 zDj|j0d^V3qnBcwKs5(-pGlYxZ%+J+^BEO%$8B97M^h8q#i|mL=HAi_-ifjo&O%Na zYsoH~vzdjoxO`n%iI}<{tTMX9+YF zbLP#~s?fFDmZjV!<&WUhH0vLr;;UUBE+=4YmdIlpy%3n(qUJCXE`;&5wNmBaI9;3& zp3IHfXfHu~Q$Mv5jKem};2Gm|71iO|>ai!`jHVgj2*UPBpD^JmH{IGZYIR}NAdZYu z$}mo%N3?jnJ*D)Aet<_fBNy(Hnq{6JZ;_rJ$VT<|6EpuZ5c1K5Oxw>3Q6(aEAmD4H z$2+Ou?KB}fA{wnWM}l@|-1LB0K6e^7Iza;atBe0I2s21KC8&oAokgDJ4eTtwoTyEF4$D6U4}0(;^e^+ z##91H<)=xNyuzm4sIkD0;HA?k9iR7;nc{Tjnff#E0;3jZ$?Ku{y$C--)pQk)LUy`L zLlSwq@!>!C#VK-6OQF@WpMN(|TUUB{c8uJr509V~K2JTU(Nd|mLeMuK2XC%?V3<>! zE_1Ku2DN?*+id%scv-mcDXei(fuX(x(h&bnyoh3ISAL0K7=(rgh#Ta&)?RTqTBBJT zpJ(CNFLhvk!hBA=5G59@kXQB-aQF5!kB)@xv1JWbjMf8CE}GhT8XDA?xiK;*Qz90O z@w7bFmI-%fQ(s+$Aj@M51=t{}+W}4xn-F+WpDzHu=XhxHz{wCZgf6u=0sO&+Q@016 z4$LS*2yy6vtXw`iLA2?aNoVaYsW{qaoic~#3LOidV2)DXmoltGmdoDtCTi?oH2Tc@ ziI>D>=P8?C5?(Aik>!_Hm`dtt5Gf-b(2W_wdQZ>kpiU!%Qz3T|c%7FTk$-F2p?^(* z?i>~Cd9H>0dLn3U+2OS1%iP2dQY!HguC6{@j%{Xmgl<@QC;nu6hH=m2q)Sf`mh)EcB4yau)qxjA!X4RSJ6;LdYH(RGl z2eRHz8EPsXX|8Do;Vbf7TxSe7;B4;M5*dj=CDZ7648%*JJ{nlfb2$4}WCXSgXLQ(J zlL^&Np1_8tMJ0D-@cbOw;MMKO`YrUf)gTGVjh9TT0QZ~b2B5;-_mA5kOSS^JQFUFl1|EeWJJha$sC}Rp2qs?~0Znyls`F1;7 zflAngOt_r)kPqwR_ z=qhu*_!2zD%y8|ycf+O#L4l6a$=tePMyW+{E7QxwLx;ATXWSAjrphl|r#}hYZW!3h z`4&~V;7(ER>Nwk}U&@I-47T5|a|rh6cBXd>#+W_*rDJ#tw(gg4MQbdub;bV&>zXOJ=_5M!A$STOwij(%f&M1eLNWfSmF1FSwg+ zq~FflA{t3Ae7o^>9j1s$mtZY!zya*kS)Z{O`RFU>)eE%B}RLNp4^)Wogf(y;5BH>%qMG4ugE9yk|c1OP4OuS zHF>a&E00)cn;Tus$QM(*%!rwb#9cTlT;QBC4YLhkp&D3hbNkVlQ2IkWi=hNjnRMA&>?EgBirva{!Hkw%Mqr#+eEN6KH}fs_XQ4~ zPtG#jD~B=nGq%kgB`$z{0w<>2NqGHBgLO^(U=>-nFL>Ovx95;1_L$h-Et_H=@zz#2? zevKw>W`UGo%|pq+gS$HfQ}-Y76OsLOK!{v@=HdKw98*%g<%; zGZ61Awv>U(D@&3VFAICK{XE6Q^0AX}QCzkmt_$fS3xZHw9Rd#SO)@-f?>EaPr1t775k7AN1G)89X%XwH z-XA4&)OYxd1$C;~_T1rmq-&~V!US#6b5%$s;gU3HahuJe!sDLvho8-2cLvDTiYDfl z?<`0(G@q0@s&Vsu8dp}lRgb=V9H`m@fZRxi5LS< zXsBNx-#>qc#rRVWhPWQuizQ%HEfQ$+&rxM^+JZCO}N9 zIbyE5fU4lp7^RBS{v4~}Dxb%5c!91xg>P3Z>3G9TUwI%730J6L_Us?$_|W%&oxEZ^ zOpKe3k=5Yz>#>;q#wuL!UTReR9bpCH5g_lnN>Uc$`iWE5s)N;QlKBf)(TM8)r^m;& z-4H=TRH2*mpFw3YP>ISbhAu4;x4YalDkQRE`(w}QO)&AONK&)kP1Ao#)CE@Wg)$S1 z8pNy)qfYyKNzeFp-R}#IDqOWeiyWe*m)_17<$M`BcHI0!9+z(&TkuIKSqhZTCdKJG z)$i2O+k+&=Wy;*DiuLg|!pT*;(mgD>tdu|56UWE|<+P}WvRp~yw3MMEkrUm}s~(n@ zJ?I=0E}*O~*tH|)QD)fKy<+K_u6I>52laS%SU102fPPE$>RNrXB#eGengUZ0w?C?? z&e7ZkvfZZQzK<0`C3*5p^NZj*5?e4cTsa~XLINde_R0n}prS1`zK1PQDk;1XIv4s- z9476s`D|gg*KP|yp~)m{t(i;vHGqnQOLowFe{j1}%kcwHmcpp2j}2xA;$r`Dw`X8} z!>DZ?^S&ndhXeL>l#9DGA!J|IC38HwMnj%nWsnURHYe}^VP zKE4pr50sH=hbxCosPLY~cVW$|St5st_KR)mixkLjqw4bkT-^~C9)TZ|pj!iV zFI-Sz$jD7FZLwu-SiTwyUS`aFt%P{aP5K6NmzaraVBQh*0&cto^&E?gxD439!&|{) zum?DnLR97%JVfRK&Q)jcN?NUJKFS(9?}#UVc|ihq)oWd2x#hoXr=7d~k{AURTtKK! zv0*?nM47pQ4o);Sol~ zHI2XU9O#KI1pHP&{E!xgA*bsQm2tqyH8zSFrOvcXv}#)n#FAs|Ch?+J>GRGD~2W^#-G<^a3j3f-)Fy@;GlgKH2aR1Ob=i=l0 zNFZ&MpV0)d2iXo)`?7lM?xhg@j~UxcT?hg z3nz_%gH`P}CbvHvWpG{7u-zz9qynb#Uz9HZ@YD^1*#Bs^X zJKr=fm@c9hk(T7cRA$?2dGPnE1{g{AuV4$bX|lv!sGKKd#D%s?jPSPsLk`VFzR9*; zzjH8La*K^8H_6oBbQdq@bMKMrWf}JwDV(#H7Ky3;j;)_Zl%J_aRl=0k;w+g&LHXST zDB18ylRN#eMeQ@9yI$<0qPvmZcrE63#v|j%VtXq%bAXBnj*X&jR+%GZ-YE!0l4)Dwey>RgQ$slsG zyRS}`p6iEC?ePay{PXk$1uboq$pT|k!Dwh*&MR|_e5(oUYcM2Z(j5I%mDMjH4}KmT zy?^#E2Y+$#9akGj5iNOj1^&18K`JM=OxB`toff-h@hBxYNj`3)ef43^VZq40$DMDL z{JzyTBz4A*F`m6k1Ozj6+$C_q{A!onx!NGCZ%7N-c2 zkv~Ileoj%G<-y_0p~Fw#zY`Xu*1rnwFb9&{yZx(qV6(_Nui|Mb_bAM}Gg9|CNdIi` zb-LK@as00x@bmC4Y&bzr3hZE+|G~jlhJQHtd8U{8pB%hly~By-+tVYWJU3HtczL2v zI_yJYB+hU2lKs#t@$zA|f)Z_nng_l+#Zrt`_?1z~Kfd)(+-6%yHIDBl9-%m>aIm z3D+GkhikgK0k{At^^}8-`_TV{5kc)p$xZF}Vuk?@dBw4=pDR8{To0`d zzQ$|;`6J6$s*h3RxbI}l+}|wef94!hj51QJGHQQwXs}P<2i5v59ZsQ&dR9sMIH;8s zuh*sG5*8Yzm|db;mfRP>TjeA)Fv0>=E@+}V8qbN#!rAGbXAZl5NKYnnZvh}8l%Cjh z-g>#|Vs4T_^M+|!I`;*#|LiybCGOqMyJ9B`(`3#cT`$7VOK-5i$ry<<&fTM7Ya*}_ zZ4I)Xl@3&XHt3ZS?kYbeN4pZBH)BbAaeWbTmxFqL#jFOQDyE*UkBNI5 zFv6@QN==2-VNg=25I#-vS>WmLdO9Hj6hC@BxS5!iNA`(3MaHt7rKQwi^WH`KMNLU_S~%9wY-^S-}NF zSTt;Hj8JdgYpx(2b8;a<5(f{v;gR8H>iU+c^WAh7%yXfruG5o4D3J2oujt{ZoBIBA z_I$+e$C$T^C0k=rl|x^nY?AT8$x6}Esa)AMYAzCrN7AK-(6b!$_k=26lNh;wxKW06 z4C_o6r8{)QmLmU_E@XMT1Q-)1$73DPqiD_c%{RqT-0S*bW6V;L?xrsSQNZ<8D|A>U z-an+Yx23+Fi);>|H!>`zbllb%;)N_6!VA)EA53LMVaHTudsTbhL*wETXFKG{YBTvd znnM=Yh$GV5{Ui&2ws1xop*WBxu6SHloYJtXH5y&rBBwj1)s_6s(g*K~y;Hm16i}N+ zYZtA2Xg}Fk9>5`;(@<;0lnujE580Kms;CL zeBD#^(`32zr!^Pt*2F&RW{6=H@#y%IE>uA-w+nO|0Q_k;@oVS05DB#3TIF9J*TW!)YZp)CjW`XO znmU^=w$rE3Yl_fOyzQgaG~HaART}70q3dQ0ViT|Llvsjt9;5RWeEK?l>LW)DqD;t% z9|tBxASu{^ zU%+?*CW81aPDIHJLPx<-GeS-l0S3KMyxRZ^wuPbNFvs^<&2itbxWl%$`gr#>9*&*| zV?{Dulo$SArkr2LLukO|W$6XjV9>duafTa5u;y@Vk1Q{GbNe~Gp|m>{K{MEbvvS&d zYyzQc++D~XSh-*54tOz5w^iCXyz15}JYBmRPVM6L=6ygoCi!!2XXNN2z*>4)xd#aV z1QG!N#Q!+AV{)_oeHG$kO{n~t;V&8aI89!0n;9+mRO&wD)mD08?KomN^}5FDcy{u; zU3u!LxF2L|w%=49e<~cY%V*T&lZ!X*0>X~ne|VVPD~;@Y<1ib7Y|XXYN^iLuVJAJC z_oHT8_Ims=@4=kYc~~(T@WCTgmq}SJ z>n=Uba#uI&cUj2<25Z#lc|dBQEZ$|97^umi4#Ru1RZK)T;$pj7NKl@VQbpV&I6CX* zj7v@%u&(6(onkEDGbQYDr#t4$GvSXnjgjbKnrN6~He0gox$#?mZ?eCfyUx!C$f!x9 z$N2-rO?4+XPln&&=d(`9XJ)O12hHc_HL+!z^}a*;dES}>xSq63-dpB1!A!b5#WYvU z?c*O5^N?8@16Bb6{$%vc0_5fV<{F2sPnHT zZgnQXSIMiF)XiHlzE{=pOuizJG2s3h>H0P{e08Kw9c%gU*CiR`%V{=@E z=S*j|{cLI)`{a~9)*Ws6!ZFF8ep8(%E^_6~jPI9E0@2+yvKsVi6|A*=*o>PD$bXi&y zR&KVW=evO7vAz}dlN0qZ9MNViEcJquAU+8hlG;4uidPd44wg=B(RM0l=sVKYa!h8J zO*%Cgec1lOFiRX`A}wxWk(~QRWN`EYr5}>q6ee~1xgLpJa7fsd8>Ji_xoE!$EFX2( zQ!nj?7fZ5|yWyyNy_jbAVc=*?pLRY!@o0Sb$P7atvFU}A;kgJCGq+;u{#eV09lMKT zJBL(@7|9QX|h_@?&LPDZIxY=tO#iCqTtKyK8ElKT(xrAs~sN{;!QC zynpR3p#hGCpP&AHpXmSFUxN94f9Y@EHGc>EU)xK+1HgMufI@%ue+K-W{d>RyL!Wn< z{sH>yIR8)Rj(;0C;Qjr~fBCEa{?;GEeK7ZVxc?8_Ux!zJ<%%NxciexEvcTNuQPw|j zf30u-%56jY@3{XibHUu_GWQ?2zt$Fi<#OZvciexM7+~&miSZBIU!(e8xn1D!{`U{@ zpFZDz2X`>{Ik^7=_t(qKpWH(Kw`Bjs{r%ztW;|bfeqq4CKL4-5gYWTB06;C}^Q->{ Ds}EkQ literal 0 HcmV?d00001 diff --git a/config/alfresco/templates/email_templates.acp b/config/alfresco/templates/email_templates.acp index 5b1348edee77d00e5bf883872bedd9ac5b259978..66396f4344fc103bde7b7e41cc8d6a9250bcbe53 100644 GIT binary patch delta 1198 zcmX@bdyg+6z?+$civa|-wY{?fGoXY3gD694ZenImd`W6xYO#K32qyzG$Ep~w zd5@BPLn~MqzA=h0fQ%3UYT{sc4K(7@RSQl{Mg|6HCI$u>2GPmu7$u|(^hzp9zBVH5)Dnk}ZaY(?(50Q8+uqdAue-T*vf8rL#XNJ`k4$>^``wz|{#KiI-88BB$Z^X+^V1Wf zy&hr;E7r`|aengd{P=V0C(VBJclTB0=C7A(b#)vh{@2e?=kLgxnfm&xmFk{r2`hY- z?mK@cYnh|x;ec1U&njfObEj>6BKqP)lB2ulq>NzM^2;ZNYf4G|)+=vM@aDd?j*9rr>EH&#MP?y_C7zXznFt{@~sR} z^^Exo6nmEY*ci2(zH*x@`}b|pH#bkE<=)(ME9pW?-LY#MqV?5Q+ML#U{l?lXt$fFa z3&r0n;v9XW`c|)r?}?kBSU+(#L&u9#@8+bPntI#h_yLOm-is42h_clu9@Fp~KRl=8#^`$4_tr~4&CriaW@T-7SE^wFj6CO0 zhqJW0ifwN0JUQ>P@EmPj(TtRYZ$D&Du3fGjuwCE>`{czL$AkFvZ)sX`?0nO<^zPLl zkCd<1?mehW_tAVkspHeN{A&yMG}q6ccsRns_0QhRHuAd-??1kj+9iF*bPeMjtrLP~ zGa|Xw%G&<6{JDJf>$-gP8uN#D%m1`nYR>b0`J<$|=|}Nbi$&ID?2%SJ9^tBPLC-{U zyX+P>TjuYTud(lYzDT~EE&W~DGv!Zb`%f(XUT2~B&uz2t%zuUJiyu6Fy4G*W4C}d( z^~e6t?wPx)=IguIqLqr`4eaaD1U~cneY)Kefye$4E>(^Gn3V`uPU9s_PW%oV(r0znO=Wq?7ZsJ zJgF^m<@TA2U4?GxWS!K$Fp$r{3`vQib zERiK5=~MQ&Y;)^ZjApLdU@Fmiq(AbMBbs7I9-FCw% zn_nisy7(e%UyvCReW{7{v{n0`S{)ObIpsRHT~Y7ujIX1m&ki#ZPT4Y$I>zm9yzyR z{)GBHfA`!j=igWP;hy!s<`fZkW6K>E=L!B(vgPggo}+KezVe0FVUJULx_mdDPsu+g zxxb^@o=1G4d|OyrR`xROkC(z9o!nltL!$oQzSW&J|F^8P{7_itFD$uiTH2-e|Ch$A zYsBvk_AXwu)KcWr)#&IMv#&?b@RvWAlh~K`ZmZZclLtk*KHL7+FaNbK>hekF!wHG1@QpuX`KTA diff --git a/config/alfresco/templates/email_templates2.acp b/config/alfresco/templates/email_templates2.acp new file mode 100644 index 0000000000000000000000000000000000000000..69cf847361f6b9bd8ae3fef01374ccfde5e5675a GIT binary patch literal 3958 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 Am;e9( literal 0 HcmV?d00001 diff --git a/config/alfresco/templates/imap/imap_message_text_html.ftl b/config/alfresco/templates/imap/imap_message_text_html.ftl deleted file mode 100644 index 461485d774..0000000000 --- a/config/alfresco/templates/imap/imap_message_text_html.ftl +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - -
-

Document (name): ${document.name}

-
-

Metadata

- - <#if document.properties.title?exists> - - <#else> - - - <#if document.properties.description?exists> - - <#else> - - - - - - - -
Title:${document.properties.title}
Title: 
Description:${document.properties.description}
Description: 
Creator:${document.properties.creator}
Created:${document.properties.created?datetime}
Modifier:${document.properties.modifier}
Modified:${document.properties.modified?datetime}
Size:${document.size / 1024} Kb
-
-

Content links

- - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/alfresco/templates/imap/imap_message_text_plain.ftl b/config/alfresco/templates/imap/imap_message_text_plain.ftl deleted file mode 100644 index 155102daa6..0000000000 --- a/config/alfresco/templates/imap/imap_message_text_plain.ftl +++ /dev/null @@ -1,27 +0,0 @@ ------------------------------------------------------------------------------- -Document name: ${document.name} ------------------------------------------------------------------------------- - - <#if document.properties.title?exists> -Title: ${document.properties.title} - <#else> -Title: NONE - - <#if document.properties.description?exists> -Description: ${document.properties.description} - <#else> -Description: NONE - -Creator: ${document.properties.creator} -Created: ${document.properties.created?datetime} -Modifier: ${document.properties.modifier} -Modified: ${document.properties.modified?datetime} -Size: ${document.size / 1024} Kb - - -CONTENT LINKS - -Content folder: ${contextUrl}/navigate/browse${document.parent.webdavUrl} -Content URL: ${contextUrl}${document.url} -Download URL: ${contextUrl}${document.downloadUrl} -WebDAV URL: ${contextUrl}${document.webdavUrl} diff --git a/config/alfresco/templates/invite-email-templates.acp b/config/alfresco/templates/invite-email-templates.acp new file mode 100644 index 0000000000000000000000000000000000000000..3e6148173e0e6a89a1dcc3fa327d2a08b53c013a GIT binary patch literal 10758 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 Y{>MW>LjC$j!MrsOa1am=s$WHq)$ literal 0 HcmV?d00001 diff --git a/config/alfresco/templates/new-user-templates.acp b/config/alfresco/templates/new-user-templates.acp new file mode 100644 index 0000000000000000000000000000000000000000..f82e6de74dd72d624c66c559df6357248c44439a GIT binary patch literal 9107 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 literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail-service-context.xml b/config/alfresco/thumbnail-service-context.xml index 23887da073..d36c0d0b99 100644 --- a/config/alfresco/thumbnail-service-context.xml +++ b/config/alfresco/thumbnail-service-context.xml @@ -37,10 +37,11 @@ - - + + - + + + @@ -93,19 +95,22 @@ + + - + + @@ -126,9 +131,10 @@ + - + @@ -146,11 +152,40 @@ + + + + + + + + + + + + + + + + + + + + + + -
+
+ + + + + + + @@ -201,6 +236,37 @@ create-thumbnail - + + + + + false + + + + + + + + + + + + + + false + + + + + + + + + + + + + diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_avatar.png b/config/alfresco/thumbnail/thumbnail_placeholder_avatar.png index 028b37f17d797c7ce57e4e2cf0ea20f7627af380..5e6fea36eb0935da852e6fdcf967b4275a1c04fb 100644 GIT binary patch literal 4466 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�(mZ9x5i=eB|MGCPHq;^{sjvV&9@yPO2-yymZ=8<5QGSKc6Kt&5D{LoI)q6K z5i~S=ZMw=93~0a6NbfT`Ix^3oz2eQ;Tr5C7V3LoSx;}QoP25)`k{cLcB0D!|2!B%9 zem+`Pjm8!WzuPsFi)&4otCW3Jh;vfQLo{MAf)A|VN?ZYY#c*Z0e&NCeBoIIHSjvS2 z#R3p^L=&Ac|4_mq@^^|d0Qv6-wO!ZigKPzR*-1}j!GXzRAYz23E&#|UbT|g5k40vFCke+u*{vF4Mhli)wDkk~5879zXULBzoE-y!t zAPj~=O6@L5i*P`vhQlvN;oA^Ym>cKr@7wSF(C)G=v7!&7k~Oy5haQx$Uw^T)vyJ<5 zplUzkcb%S`oZNo!;DOEsfa}+|0)yj{2pDDif>6`VM`1>rHo^r2BPZMTo5fNg$h*~UqDP6QrsJ`4;J{KLQT?Gw1y z^w7}IW8OZ)V}M7EM}L)1DO$WW(%Nlauk-O`evd_d#xjhQ@3G5CR+~r50g#^sa9n*! zlqv0h-6xqJS*~8as_x&vU$}MaR+ZVj#_m&L3n~Lkhgh36&T=2@vo-E8(d|8a_^>rK zHKntkUl5ItkLL$~mZ_cJW`% diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_avatar32.png b/config/alfresco/thumbnail/thumbnail_placeholder_avatar32.png new file mode 100644 index 0000000000000000000000000000000000000000..48d7b61514485f1854de30e01b817fd76cfc7257 GIT binary patch literal 2354 zcmaJ@YdBPE8(u|{Lrrv$s4;Zq>&u+XnC9Tn7{|&1D5*$&QlhqFBtzd!JMI1B>s!~g*84v1b>H{%yw7w0SVz2BZiafM zdH?_zdbl&#>J_PZb#>HVX-v$RdRYOx1i-%H5Lm{6Kwu+Z%mtAiLQW{i208rbU3Wnm z0BE5EegSX*(~H6r3(*`61}zs#)NBBtIm#s*UIYjuxnQV3WRDuGzJ@{y`1UA&A`{D$ zID=sV_ZSHDjbZumVj_5CKFZMnNt0950zwexAmzfHA}K{~kNT=hQSUX!7!>j=1dgyr z{Z&)|(;Mk5hCn0{jkV!n?XXBY92#d!AdyJxk$5Z)i^1YCIJ^yxM6tzFuvp}`2c^yi z@k1zVhU>Rn>c}1y2E!5x1|yTn&@x-J7z)MU$Yioc1CO^+BW$G6BA6q$5lJm)6&RqD z2MHvwKrBLP6ggaRBy5jTSNhi#gp%*FBI&m_sT+oob0ioX8mqa|ERf0k|4^auJ6Z~} z!N2SMpTbhVXbFg6gHmxM#8VG0#8Q(=LUD#b4lIWJ#NwT^UGxqU!(wTeSb}u+B_f#| zo<(&BgzatFeMmqRz|!Awd+#cZI}4+_XH<^VA7oCjm0pZr;&{|+xR z7nAPPr}h;Qm6zrdUI7JVb_G7q#_~!RZAsi#jv>^hnItYs%O5T%t`)?a7=P@_FFLvG zb@)7@lAX_G3c?9Y>#~PV>-HMRBwq%jLT?YIXLS9%?uB^^vo^geBV%x?X+pfmt&AD? z;X{$OPWGx@OFYXeE2&(|synWR6LFDmj_{$pYHw>ZNhBh>2!HMiJo@I*+tqtV9-V+UfyiK0MMYUME3Z{` zX08dvag2^ir7hJHwiz_E#3M}KDnqs@;oOTW(}d5Pnwpr0+}tpGRv(GK1k~5A1)6X6 zB;I5T6w)aNfC@DjDB%bR@Bt7@rS#%?P6X2rK>-2%$oTNQ zlmee{f&3>w9dcH@nW1HteM{-UW_g^B)jW1#ZhE`XRG{5Nh2xHg$%xBu7o0s>y+WYf zA9eXk`QZNBF`IGe^A!}EW~#OIxf5YWeGXr_`0gm#QnzjR%w4kTSY6c;dWwg zdAys(zoB+HP1H9>_Ri@_?oBWnif%~d zwd()6Y16fa&t``=zl+V+1(uJGk9)m^CV#>$NF^vNhV)Hbfhw((g5k#EYX%2?aY?S< za(8Rct}6k>@{-Kr{m;c` zp~kREmFjM`BI|y0^9zOlUhZ1Ifh(`)T$NLfo6;?F>lYjwoupgfpyB$}L#GQbPbC5f zV`Il8|7l&T%zM*X4SKg~mqF~dt%~&BZ{FoRXk;6IDfYb{i#54XyHB*XM7f2k?>?`g z?8@5%U9Tjc)>vP$+~<^~k9XLaA7XjDMfUXfd#i%Co>O|}i5iAuYrO9yZLg)xFjbu| zv)8!=Ed@|}2D>I?hgQ}PDt=oY`9_&(aj8>SK0as(Xv z$j`fF&IfM=Eq784)Ey}4b4IR8iWWZU=5|S)K&sE<;0vt7I+b&GP1vU>{bI`Z?%etQ zM#rZ-GQf#G>Ymcn7FVnoNKj0|$n~CPS~vV5`tA#q7yc2FDnM+XMxv%LdEu4OI>hYaHT2(LG=3`Hc3q=3;B09*}`p{($L|8|l z)RViA=$exu_Z#^0_RJq6$GwuK&MsyVSQRAkV#i?8cyCKb{dl|n-Y>xRbAtAsT}9QU z2chBqpWU+so50^YR^>oFNu9}vpb);UQ~uzST{YE6jVe!3ZJZ=DF(EF{DaWlCC3)zY5rn7Tv&{Y I^x*yf1rN&V9RL6T literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib.png index 86e0d09bc628653743ed915b5fef460aed6091c6..484286f32b46feb4f7be4acedf8aadb6e3827922 100644 GIT binary patch literal 2667 zcmaJ@X;@R&7QG=Mgfa9HAykk{cnqRsWJ)xU2_#6Eiy$o~Bo{~_14$@BtuhHxQ2|R0 z2$o4it3U??MG;#olTsB#N)bdrEYu056ajs~s(nA6?|$FC=j`*Xwe~)HuOBxrIB=7( z!G{I_02p)qIHB5=vGSsj+V4qm@`!e^RQW`z!enu(RDl8lm?Bv$1ac*UcqkMSh|-d~ zpp5_k$BDxuRgt^^nouUe3sx|AwM4FE1HeXawOk-fgj8TG6fc(2agSRs;y|&8j@#nK zBl6^IC_(JET>*t{4-6M>PZUx`IBzd-qnf4_kU%N{sFoy2l{7US_ga^x-LD)IaNui* zDv^%+yQoNBFvylEAkYm@bP*EWiJ&_PPolVbczCP_$wU&7KqM1LWEYYLjY6goiQtaB+4kspK@a0urcXig1}M>1`K-6J#oxGC?K> z*AczXtvGNb8=J?K|6QgLlSYkEh#Qm2q2c`nOK?rS=}B7qysXLXtA&nKS3Q zLrz!3OdaUE9C#rJSfSJwo&v$9rwY>dLfN~~= zc24(ESVjPQYs=WJh;Qb6e@@wkD=$8(A*$*PBurD{=zJ75=gzoO77-&b{VU`{3t z+SRQ7Pn#?}=c$8F;L=wcCe$+7FBg-?eb<7X)U_O--?pK*d8^IY;_mbQQ1OVviE!t_ zmiis_F{dMbsfpN!MF6$(hQ$YenoqcOuBX>^9JF~>NdkiQcSz!oT)ThdIf8nlPW1dYthP;`qi3x^wV|P5EEe!P)1tWUYOo4%!n27tge^mF z8JgjH@^eIpdI>9nzs9XIHKENg?M!Ryj0+zBg@BmO%{}bIJXTvH%0jRqwmEjf!Y_?n zh;2tsr`D~>Vw5j0Ke%DHTP)@pmBP9hc4pTxRrCyD5iB&VODC$djxgiqL|D#N@Zj@j z`=re!Q)#2I7ug4r()Zu;dg=XC)_1-JY9jz=+Q+|viI^!H;(ww`pl zqJblBPBxzF8lMG^o1it(1r-y6_r~#n!p_MV{7X|`&MC8K22gZ$e%``trh_)rxv*`uvOshbjgNPj&pqr5~hzhF^1SEsds_y~I6e;K$o7%i9V*8!j$yO8>oU!cD*U5eq6X;%$^`*mJnzhVz`o`S;I7+Cd-tI(4ltcQgQ7t2H)%?fZz3#xk5^JMwZ|_x3 zP_)y&lQAdUOQaM}-){a{(~fgy>x~iiOUA{BlcfnHs5HPU4@kFs`j>ja$VrE_wR@|Z zzGJ-9)&*IZ)TJ00fHgj}+Bad|KVx^uwlM0OHW+FaE{~cTOw4gARU5ZHwmi3JiLSox z+G$BJ;&@XyH5=D{OnmC^wK-vzi0=@bqg-F4E*@*5rI-vOl1O7LjHZuDOExXeZ5XI! zMpLqlr!<jegVna?I-c5q}>-R5!o9k)iEhSowGzrG3b?-n<%Bu4}81&RIEPD%ka<6O}*8fWX$3b#? zptQ0zz2|%NYA*8mjMa&>k-45ua)r&5hM0bk3df^$9=CPyW69WH45H+6i4zh7%do&c zizI;rRQ|fn>MM@u^i!6ZCX?3B26YO4&Z)d$fi*VeJX$|6NNGo~{)I~JakwrElox>6 zI8jC>l#(T(aS_JEqTKKSbGt6!aeWionx!F;)s zqFd?h+i-8tYWdQbs`{ZuK}{;JMV<}d7d5}q#R9!&PIX)8F;{*k0j^IVr=G>n{14cl BJ#qj5 literal 6453 zcmV-58Or8~P)KLZ*U++PJ&B!vARtPXGY`c&J5XGU@(4Uy?;WFK++CDD|*qrvLvF2LJ&4EFWJ# z07Mf2h!LyU8UTnPtJo6&hzV>a8vuy}0Agq)H4Fer0RT;wrw0W9sR980h*jJI0Q@1V z_$~nO$A?C+0gw>@oN#PdR4f4g69CRKj20IPz^@4aVPSEhF#!C>0Z^7PF^n((!65)p z4P~;}0D_AEpy=c4C$}m&@FKu^1OT`24;QBo&>##TSN;#D^b>%@1#mg*AMT3>AOL`n z2IImF^Z@_>;k5<8zTj}aC^w3?b__cl^NI$@T+OO8U zW@rQe1pq{FfE@UW1S2m{(P%DoJth*XhF!tE#CLJk6RvPK@Z8|-=ldvt7t|E;7S0ms z5Jizp#nQ-kC8Q+7rJ7{8WCP@y6^QFX6mKdMRT5PnsHfNfJnX z(rR7mwCaWGzc-9DUcFk|Gi)_Xi0f#;m3`ihnBrplvZYb zb#S6M8~_jkGf0Aah$eChMMj-Ro1WwqpD<*O8C6wQ?ql{-`sHHvzU#;_Jy+fS!N_nAJg zftz8L(M{uJ6ARODvt08#7W0;3RwmZlZOUwC?R4x@9J(BNoi;gVy9~P#H~DQo?>_Hg zxHZkQ&r8JH+vlY3h`&MrJ+M6JZLk_OI;1&tfo2e%644kry+b>iMlX&TWe79vcP6o} z#7*oX##<$@6N{5>Cr_u4_c-ij@5@VVOdHw1k}jWNbs+p;dS=<7y2FE6Q%AY7C5}lR z7tbN*67z)eMNW_kh$jULxr(6Z*Qw>=g_8NxGo|m&JU=^B)^YCo`R4MPih|0+7Y zUW~Zpec7ej?22j)`A^)R^R;j49@JlNxYSs1HN9zPvtNsStIl6yZAjb9wXy5{H|lQ| z-AZp~b_Cp}bgFg2oM|WYV55Jc!(w9tr;C~GLEMAUSF<4pp)x)84 zH~>HZS+Is^I14WjWh4Q)k1|A+p-Jdm3<*<)HN^JgqVWRwHm(?gBH=Z684rzDk9UQy zkN=E7yxYD$P`_DD7Y!S6yp8BYh5|FpU<@h=__zib~m$6J10vi7Ab(WRx>6 z?5t)r$F;CqcQwSrpe66J7^wH9!Gf`)~ z%dF3-o);-c%I7OySN30Msycr$|5C=~gzAVZTWT!+RIe4Q!__U+ziQ}h?7CXpbh^2y z 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 P00000NkvXXu0mjfav^gJ diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_aep.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_aep.png new file mode 100644 index 0000000000000000000000000000000000000000..f439703bf44f1c74bc8cf69a65cf5bc5f02c2fc7 GIT binary patch literal 5946 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_asnd.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_asnd.png new file mode 100644 index 0000000000000000000000000000000000000000..f4113fb0fd25db464c765bb1d5cc04dd2a58d329 GIT binary patch literal 6616 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_au.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_au.png new file mode 100644 index 0000000000000000000000000000000000000000..471bd7515c93f34a560b2534ae74b13391122443 GIT binary patch literal 6854 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_avx.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_avx.png new file mode 100644 index 0000000000000000000000000000000000000000..9c033a2d0c412330bb4ebdc2d535f91e05a6e328 GIT binary patch literal 6080 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_bmp.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_bmp.png new file mode 100644 index 0000000000000000000000000000000000000000..69e93bf54359a750f57cfc77bfc300e114725e68 GIT binary patch literal 5873 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 UDHUG-^AlP}!$7@S)jI5d032$d+yDRo literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_css.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_css.png new file mode 100644 index 0000000000000000000000000000000000000000..e6335a13f7971fd50f645f5b121aa16aba2c3c8a GIT binary patch literal 4821 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 DwoYK` literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_doc.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_doc.png new file mode 100644 index 0000000000000000000000000000000000000000..4054130eec9943e5e834e9b902effaa03a048d72 GIT binary patch literal 4621 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 G!~O&3<|94; literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_docx.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_docx.png new file mode 100644 index 0000000000000000000000000000000000000000..4054130eec9943e5e834e9b902effaa03a048d72 GIT binary patch literal 4621 zcmaJ_c{r5o-+zp;jF3>)G9<}5GYm5#hOvxn;S{ouv5#S9G?SeuOUja6_Ux3ctc9fL zSh7UQz9l=UB=zI`PUn69c+dM>*Yn)>_r5-#&v*UfzMd!(BV7(QK{fyYIBkT?ADCU=(vRx1d@W z8mQn&L^+2;j2xZleZU3)Rdu?z1Kxu`g*p;k+`QDp)|;M*LEW6x#I7I>VTRsX1Xnjb zKQh70&&V9_=YdB%iK$s1c(Hx1C&nm^rEQH)x`eNRXNxn9?OeC|AJ6G)WrT9 zl!c)QREtC=K#_7VSv*V$233N~!4(vhm6c_n2pAkD4@1bq5wdV)6$OL}3%dYIbEQ(fRpjMqG@2YuL5@Urk%yzv=tB(zLiPY5OS$evb)d_7QBMC> zz!E5UvYR*6jpPMARCI78`BK%y4kG<;3PkUJWW6YVm+7Ej@^lApdAJL&N_c zN+kY+rcg}@{}b>3DNHfH?oE(4B~VDdWc)$noKGLR@>bCz6C9`{vN?(5`MZiHt|Tgn z;!5&{YMCLSh7Nc)uS5GUyrH2A&Wl2I@WK;tST(T&1vxi2Cl!nq60L|p=qR8RFmSjw z42{u-X=@@;I?8B8I2MNd&Bc=NzC?l-^*7h)AFcxIU%7{)K=eNFj3toWt`VGc$Rr~4 zuas5X{yi4#zv}(Xb^6y>5ZeFBl|KkZ{;;wCtI@xY4yNaD`_I@O82=1D!Ruh!$p>St z@L<{<060T&Sd2M+Z0PD$PxF!EYP8PIZ^R8oJbt?t60QNAN>#d>tcSbzQuo=GWl8*Z zvmMc$d@=WYsnP`V(o!AwU){RugV{e7cg4j;4Id1|N%ZsHhBMzFvUkqD{06`KzV1tA zVBmsZ=c$#nDdDA$6aH%ri&-^aCOp;y*KRNFJ?*-p+NY46skS_=7#+iAePt$3j5`Y3 zqSrASm??B^gWloWpFA6o=6}H*^t|KJq6yE*lPBHG!mB@gx9Dc$KOJoq8=npzdi&+e zmxo?_u{Xx%7+F6H3aqTD zsgZ=rTxp)?svek(9E;u(KJn{(WLbJ2IOb8(h}Ditl0K8}6YlUF32uo{ysvMao2x5| z5jxSjy-!~}>HgdbW0A;gzr_Ib$jYjRcVAxiHn+1Wwk><-nqk|t=(n2AB>hrKT6&K& ztD=49>c=g++E{i@&dw*K$t;;9X6$7UfQ+`Xv9!EC>Rp}NRd#Ya^`TDwoAT%geOY<= zz99~w4q;FKqgm*v7wcy?P?v3*u2rmH7+M8ISwlaYy!4vvg|xS~hoxm__cd(LRpq`8 z!Wj6%Eo*tgS;y$Lt)E+YL0l#f&01c7*AbLgRjD%BMC<#|==<8h&@k=YJIl1sKlk?x zd0%d42TBAgd3|t|l9KxKZN*iBPl~)vLPU*5#-DfYpYPde!zd^y;8n^>!;gNx(8I8p z2jTxEd;Zo{Mn=ZX`{!we&y0ZTl@y*?ACVJ6kD3X=EkScrn8DG}`fy?YyCQvJ`fqKT zXfE;jpd2U=a;kYsy3|k?n7@yuR{rMX|zS1BA*gDZ%v$$Y{S3C{{cc zR$Y>dFqvNTNd zI8cw(_R6izq=(p*K*F&{KP@l!baaHbg%!khpwI$5z);f78p9WY>vBk!E~*VJ(K7)Nl)htrfFnIpheU%DE6WkNXBM-T+EOV zG}(B;Ku^4$Y@K|_P3L8Jj`Hn>5T0$JpAXWQLkQS~n`EL`C0&zY>2 z?wKBVaB;K6|EomIDVF;A4-FK@b!)ESpWu}}eLFji+p-qTRhT!rB0-WsD=EVecDxhZ z{5Fi;gdT1ivWB?A{b1=@r4t(n#lF3Z)(AF3PX}q#uj|FlPu~*ZWQ`MStrj7+GxoFT z505_8y(4_AH|O5&k5ERIIW&a!=5C?pg<_0Krglg!Em{DzDyPw3h=-0O@-G{fL7oE+ zC!huTGt3Qom!FvITaN$8SQLa2c4=n=7EpH8oo$d)zRE;80M0v$=2i_9S(Gl++b=IV+ZSJq#Jns_b-X{GHzZ%Z!O z?t1bx#~@j}X72G-fBS>+5oM%OV+Ij9KJIWQXWHihlQuFeFjay_M*Y1r7&eJetI?7$ z5`-h3&!OD86CB?wW|)#ZJj}$*SGRz?MS@)O3|JE#(*L3c2A@HW+YdY*Jnc#^mok1_ z6h3YVXLI73f5&bHN!~81h2;3soVg|1^?96hx-?=aN7gSi>~WoW6CxA|oW)pt>O(t> zd7ujd_}sHXK6%`RVzoJ1LA=79kXb9jZhbXskmXp%Rq& zqm`#D&$yC16HtzZ4I0HTPOcKGQ&XB~QQsS29dwxcdNp!y;}l=PrTj+o2^X>B69mWe z5t#Ff{-d2ALWu!nfbQNUx7f=cQ zzEnhh*e#cwi&)(sYYjnfjpE5e?FK#r3%u^`s;8Ehj+qv`+c*!BwsDb0BJ6PH1 z^M&1zn8IYZu|+Go1!&u;_%t}t;608Wm^){q2$;T3WVIP*Ey$#@#xK|-vJ=4237$J9 zIL*>AU0lM~$8}F!uCwT8Lwm+5=sPmyAMO^=OnBmnzKeU|sT$`JMPnK7ExFxw-}Vdq z@y!R9UXNO1J*x4d!J<)Be~0-`9f*lqlyj;g%R8?k7H?HfT(yu7<4Z@+9C6&l)Tx4T ztW743LG@87HI<>8tbMwTr%#oq_N1Dh;>AyM5do8%*H%WW#Eaq#PuzYDj$Ti3?cY@_ znx=dkw8j~4dYKetBy{_4{@UJMbY!djbvaISJ(0`he0T;_-i0d3Vt{>hYrjM6i+zQ9Vacs_k-KB66as+ysOtx(9WX7ujq0) z-#1CG>%BQdNWVDx(Ub!5%65ys4<0mIMfwe`D$a+t9w~nZ-m1sw*Rd-R9_?~d}$|#}r@9TnEUDh=(Qjgf;L-P{t zQKflo6zrv}W^P-ihjxjF)1ayBRFzoTIe! zy5?wjro*z$Om^b4#@_{oLF0mZ4i3-dr48Y?dE)iQWqqJ&(80h zebCd_`{pMkbS1RFFUO^%(ZCQevRKw=Htfbs^|@D=!5a@2TFnyS>-FgR;axe&lf+ZJ zHUw_GG|b+JOAg%o!W78(m6cdL_XN^(-dqvb@&V5B(v*~vKC?zXrX;Lo<;qQ*F}UzN zh%Gq-R&{^M6;byhOGt>nBzuwm{j}^ET-uW>nt)!|>*F>iq7qy|&JQL(OQIW{C0XTpr`JDH4qHpf#9q)dqL9 zwE8JTO*Mt8A#PcY2>0_{Ouc(^(ZI05#rBHaa5YgJPxCCFN7!hO5EfWhjD znI84o?$?&`uApl!wtD@t>j3mJZFB6auE-?hJEh#r&QK066!v4bhE@$O5-}H%1l9xK z@n;^WtGyHF_I*u|xijEWa_+8Pc5uMM@#e;da#O5flvDPSKGl=&TVe-NtlBj<5P)3J zaVe*XGYX+EAOe?q>@GaU^&Z8yuOIDXEC~wmkfSdQ2iB48dUFTXkM_Re9|CU=&y1=w zMj8`)ejrv?C&+O4(;G?eIUGR(>ffgHyv3h>JCgJ$!@snG93}9|S-)5$+l-3|e3e&x zn3+jgFK*!Jrdn`doV`wLjSHeB&UTm*akH;6!-n;k+-rs|bJI`zuAnnI{tuk2&(oro z^QSF!!(tKxNU*HqBG%Vxv47a&jE_w2R6HA3?E4vrqU$c~v*?SxnEFEVQQc z=0OqP&1qt)37bou^j>YsjQf)@V@QIW(JaH0kAWAojPnL)bs-UGro9gPZ=sNKB%cKb zi+8otF5lxTkf{B6Yz!Es?{_Oh8k%d?BVvFBdZ_gq82GHZ zJZz$ywVa>z0mIolU05-=$5V!gXYpa@tu+j91uxPk0j@4{srz7HFh4)vE|3S{7ZG_D za@{yjqFkc<6rgUl@QtO@tjK6Th>TFG&*D>8L;7wWT-(DH?ADFucA#Fk4>^tDj{+~ zJwCwecQx5u?c$1|jJ;QDHs2mwY6wf%=aPA9|8OUmff-oHtXf1U*hC)wpuuSyVJkK5 G!~O&3<|94; literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_eml.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_eml.png new file mode 100644 index 0000000000000000000000000000000000000000..a1ddd140106ffea3c072fcbb7448ab18806edc74 GIT binary patch literal 4687 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 UDHUG-^AlP}!$7@S)jI5d032$d+yDRo literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_fla.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_fla.png new file mode 100644 index 0000000000000000000000000000000000000000..d112093c937332b2e4fe8a15234dc7e093b6180e GIT binary patch literal 5591 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_fxp.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_fxp.png new file mode 100644 index 0000000000000000000000000000000000000000..4f4e80e067f5076bfd1f1a6703eb10425a283024 GIT binary patch literal 4604 zcmaJ_c{r5q+a6|Q&90CbW62U`#yXR+&0wrqU!-J=8HQ#IhOtDpEKy1J5F#lWdlA`Q zM5K@`CHr0^5>X%T@9q74e|+zEAIJ0D_jMoVd0xwX{c%4}f|aH5Q4UcK003~*1dFjb znDq`Hwj&4cha}&LgGq#D=t#5mA=2nLKLSAC-N%gpHX-9Y2sQ+qd$4aeK^p)7LP&Ow zG)D__B;JRtj61|A2a%}2iiPFJR@D*O0(sK5{i#GwWpu5^G<@(-raa6w8Gf2rRJ z7=l0Ek3^-Bd??^UMVy;Y01X8>i1a@xkg5O3QvCle(?P*hf^bw7m@@P*rQbjci~k== zCjW!>r`Ztx8}I)q>~9xLC8*dC{Cxua@COG+lsa@pMWX!(IGT^2osZ9@-&M5o^r89q zd-_npXj^r#1rAT59NK^3Ei8~G6n`3yf+v_@P>=%!WfI99302cD#ONc`RJGL5Fqk0} zp>GH^G*H(v(nP4iFi`d1T#OGsfJ~s!eskUb%T@nZ?qMsCsRy1h1V2(B!QIHuhYbEJ zWhCj}V}bvx-rrpJe~(4=U%4s=!KfS_?EgCG-&+UWbGZDcZ4Zop8lONp=ytz@whr~i z(*Xc(stHElE@-URIhZPFFZwGaBt*8$o#uPN5g-V zmi0VTB$TFBq+e2+;#jF%X^0G4TeQCszfSlDWhL z*}IBrBsz>yIu1_@3k^iQ0lh~U?f2a82~M#jnwgn-%n+2c(s6~qz8Ix^zCY%2GO>_t zikjI+D%<22=e!!#`ZJ-UGSf#!Gy9UR@jBZqyinFy*e? zTpg``?+63S0G@h@T@#UEO;bsf%fkehKQQgGbpg+*Pj7KY=xx9MI@MNped`3@rioTS zp<^cpND7dg$p*R%uI2!YqDo*wD8;0R0+tv6bWn_82#%qz?HT?U?%N9L@7C&*;T;^SgEv-f)TvUB+ zZQ2Q*Bbc1rhG}KSiS*`ukeGHTXcr|8k`zYV?Uhc+V(Dapn!y1?OU}08EjDV}vcS1> z=brikBeFwZA8j75<_M`CZ^=dgnv{=&m|NunA5`k_otQ>+>EGoE0!73y@i9$BH@u21 zXF%BImX6>Y0la;vaGt#{EecvIC?LyuHZe)f&FxU$2!L~I1^;vn?(9i#5&oMjI*be% zG2%%!Vir4{rg5qCT@-b`H_JLY8P3O%h?5bAdpEixrg9vhq!E4g-MxVz?i{F8-32*YtoJ^W0qV z`xD0168wGlPpHa{wkRhx%Au>!#|Cm!2gjO3MX^pBy(N^x}+hMqX5?pdc-4^Uf|ggz>>;S-O_sABj&?7-k~x{ zAvU+rAD=Gpl;tPEBn-6zmIXB*LgT^lFEdoO@0d55HJlC;R~FBC^QT0;?+rY_!7j$f zIaq7wUPPYGPT0Kt(YDzrYHC{#5%}PYX)62f*Nw?L9f<`SaT2o^q(J1UDH@HAbcMdwt@az%W3_942VB#O z{1BZ^jN&j;+S6tnIqle2xu^SzsBqj!b5RB8t#9<_eAC1)R|7T9uE5DJjwF(N&KWv) z!~Kt3pEM_UZ%5hc`c7kpJv_#(sz!uGdiZO5d(CC-vTR)Rihl?>AbL>x<`)f5J4Em7 z>=c-eGg^j)V$)lklP^ZVTzcaSR*ojeJ<7OsON6eOb%WsH5k0u%PEe^0q^s_YJmHp| zqkufVSwOFZpLA{FVbicZeQ=|yvNm`(4CH4F=ev|%3>s_4o>j^}pZWl!CwS~wtJ0ng z)g?9m%@1TexA?VHf&9Wk=GDM6FTWOJ73eZV>s;4LN8z`nwZW!I9>`$uHR;Wk%ZDeB+68_nDb4DDxJ4gRTabwOLe&(BX7`_QlGX`lmQLbfE_ z!PGhIz7U`R@gYJ1St!@93jmHT+`1ne)Z6wdcvm-Ktv?L3z}m-7cV7y^XK-qV5i`%d zjBW{?Z1TTyl`!xXTJC;iM2L6uXG}}}!kaa9Pp0^a7~xi55&&FgxY3g?969-C3Y1e( zDqEnbbZx5+J-Bm^x{;!^6wobTuAGvu;@LqH2}_uajr|t#J-SAeqL9nYt#--<7j+K( zpy!kuS`a|9Oc|I2rtSl-b&svQu^2JB?^f*d!Cwinu-eyEcO&g@2C!Hj_hw3@)1ij+xjADuni#UOHW`OE< z+VANdOAmNQI3hU`tch%ME@*rxW@>6G4Di|&Hy#U3{0JRAC=v58H#N{pO`9}pSn=VheEI!_uQeMf0b_J{2HkOs8 z7QYkxw)v>!`u6UK1fG9)Jxtvq;*wSQ%C9}cE9ujlQi-)(Zu`mIBWIuyoGtzqj0lk9^v8v@tdr55VyonQ;T;~v)!GkfW zx*=k-G21P`2c1!!>S^1T7On`{8mw^Z7 z>jSH2-STwZ(GztoL#2Zw*HT_*-dGwhG^88%*~hMqZ#$Wn<2G+fIjHjplkXq{yoPcH z7J_!7-n?yI=E4pg)W0`H>LJ9$^Zn}z#JvA0%bfUZTUv5tMb|9n?h|K5gMjqe$6Koj zqJ~lB&YG3JVf)8qheSm+I%2x$6FVLl5?n|~`t5Pg_IzuAO}3t#m}sEvgILY1y1`5V z^Pv}sgKf9kZF9xtF@Y9SDLJ|68($atBS)(j;OV?<+;O>x4q*vic%77*ESH>8F_%58`0)V+tR7WDyxKe|DJOqlIAKs~4YTTm_8TEOV+&;2<34Q~fq z^4;(TmCxHV67-6_NNK0ronE@GWjT}Bb`O5gfGCS}M*((5h|ON*Y(g?@Uxt0D-+j~4 zD*UZ<61R*iJj-B)spZt&RlEk{$(FP;Me+@pB?%6WzdyM%ow#q@2?W?^J4wGmF`iUb ze$m>E0QW44fV*SQGGOP&o^4kjeo*)*IaatzHYEheY){g@^>ma^A#(M*dIc_BEy*6d z$ZW1H(PkWQ@=V}r-|+Bo;9Qn1v+LrM_OJ6FjCzO#f>C>%1DA`O?we2&KK`8NzdP8h zn6rGouwQWodhPfe{8j&qs*91x<={_a)Q0dg=HJxf{URUQ&Sex4=jdYA)&`0jXD=3I zrVXyGcy}edD*moaGHD(YZNsBy8Q7hg7=5oe?y&BO6GpNRn%6ZVBkAjp|FDe^5CUWD gCa6z$n*l(81LSSRG#rz4_&>(P&=T|5z%}ZB0IiS>9RL6T literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_gif.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_gif.png new file mode 100644 index 0000000000000000000000000000000000000000..69e93bf54359a750f57cfc77bfc300e114725e68 GIT binary patch literal 5873 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 UDHUG-^AlP}!$7@S)jI5d032$d+yDRo literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_html.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_html.png new file mode 100644 index 0000000000000000000000000000000000000000..a7304d7e33090501227f2df2ce5ad9e40e967232 GIT binary patch literal 5186 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 PSI=1AOs_`gw}}4%sB}=2 literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_indd.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_indd.png new file mode 100644 index 0000000000000000000000000000000000000000..4d8ccdbfa163aa2d71b1dd2358b8252e38fe9738 GIT binary patch literal 4931 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 UDHUG-^AlP}!$7@S)jI5d032$d+yDRo literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_jpg.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_jpg.png new file mode 100644 index 0000000000000000000000000000000000000000..69e93bf54359a750f57cfc77bfc300e114725e68 GIT binary patch literal 5873 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 UDHUG-^AlP}!$7@S)jI5d032$d+yDRo literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_key.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_key.png new file mode 100644 index 0000000000000000000000000000000000000000..c6210577db4a2a689331c80596d2b2dc5dda3652 GIT binary patch literal 5803 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 Nn5I6oRQ+Dq{{UZhg)IO8 literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mov.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mov.png new file mode 100644 index 0000000000000000000000000000000000000000..9c033a2d0c412330bb4ebdc2d535f91e05a6e328 GIT binary patch literal 6080 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_movie.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_movie.png new file mode 100644 index 0000000000000000000000000000000000000000..9c033a2d0c412330bb4ebdc2d535f91e05a6e328 GIT binary patch literal 6080 zcmaJ_c{r49-<}zRk{bk*t3v7G||0Cbufss?8(`p*lY zI{TIooF~r~RyWKYH$$?Gn&wx z=;*;!$S!!elqf>P8X=8*5|Y=iU%v(yM<5Ym2yro_xCrt(N&#N%wR3ZGLWzladU}d_N{EtOY{ihWva)|P#KlF<5F)PLBsZ*=2+8&G z-wLXDS8Er7lN*6dg8xy(-Y2`eDe#^}`mYp-PXCc5x&FIMX9W}U!a9i|MG=2e`WvXD z^Z!GM#Q&gO-3;*mjrac)b~W;L!iyQ;UCHh))@O~gx%|hK6H3Jek98xv7?H`2e^*h@ zj_gKuwIe&hRSc!zI#_E0>5u&vUPlL|Npf|=lC1HXstUYk3Zeu84y7z5i;=~Mi%ZBz zD_iqjp_Z5~+%i`kSjtwst4tNp630asTB?{3G|zC=i{_Jgeee2p)Kx znhTi-|0`t_;h$qc{G;B#xwwCh1^JI$v9n;r{xtS~HTv(Pv+4P>{mW!I5LwU2$jR(thXsn944JF+B85)wd7czj%Z^I7j2wx>8iN zC556GpWJovtnKxl$Z}n;uaqdj84ek6uGralE;{#mPVQ$R!{|4$8CaI)tx*Zvo2?%L z!*ln8d==Fbixmg6sy7$Q79DHjsBX;N($P6U1qwN8h)f6&1CSD6BR%6`2j#2vG8!cHpJ0YE9f&AB;!M zuAH0YMAxA|;wU^KujZZPMyzgkLrk`?Mg}b{?K&L&R+vt2;1+MPaB^3uQou^_h9uk1 z{25n8U!QY|`yZhrDU>P15R9+(x=u{N#4spF;p1`3k@y@fE5E@>tPi>BiOg2n(b8`D zR`qZ^n~b98RD=KQsSd>^6&MXF-kk_MUK~!S$Pe7DQVLfU@0<dn zKcKz-k#Dap;3kpn87LTzSeoR@ikOq_TYDGZ+pb-8XdY-%+LQ=s(e|3<4Mr#_ZG7(T zUXA8Y<#rF*_)YaVHfe3oXWp*?Q2BdbrS@+4RuoNYdO8)1)%^mXNFdnhi!*FrVQim+ zUp?W+M}>QfwcDuy0)Rmw{jl}}0f*$a-kHT?nJ|z6hcd#00^myxu*-2Yn3&J_w7DH4 zme+pHc)qswQ@pm@bk{7E_PBV}2YeV}bQzx?$P1ir-qYH z+w!M*rolcf9<+g{u+v{3vXxF`R?{Y^uc{iC->scppK7$6aG;wmGm2E{(w!Wtjht5U zzjo*gd?9rt<_F!bS&h#<0)nP#sP5z4>o>(tkLm?Zc6+%ZDx&-)=_CAFes45xaLG8K zCtLwI3&2v9HhoOT!A|=jo68Iu`NXJ%u4__t-2evtai%c|-ZdMEi^qHZ@$HshS z4r~K{J+V9qRodDv?2Pkx7z&z+1Hcsr(m4E1v@Mg79Mq@ld^PH-ARJtr5R#U<~+C+LQe~ zR;O|45o9~j_HE_%kBu)wF9R<`I75mx`e(N1Keeyhqctw>^3@E7+6EqlzWUlK@aU05 zr8w|)Ub+?A_%|x+SMKKD*}Nhna~_mi=dxaIzZsbOtHhqnDtTlXxd<$){Tvwi^xjaUfY9f-r4Ugf}%o)HU zZj9y-Bv|;IQTqq3J4f>OJ@O~KpjSusqt+Ue=qA$i)Tot=l*Ya8P8KBRm2`{eow%3mv&>@)TT2>kgcuxm3tG`*Rf428(>=$pkgq}3ws zI@g>d8# zJQ&RO%O57}$3691BBY!8Y}D^hG?a4b@iNkcx=~OheOL{`77u{KCazyMU+U(-y@g!1 z_O-(Gvdl%qK%v1YB5`Q(%wVOXBP12pnLx3Uk*P5TRX(PlkI*6wCsy~{R~d>O&5n|J z;FyB9>7Q6$z~Jc$hdD-`s=2S-ZWq=V!@aBhk1`)WH!h_#wP9x99L8tnDiCAMG8=C_ z6YqODJQMt#VY9EZ-VYYMZ%35Q)#F`UELJ^tP;eJcYK8^4G0!e*x_#W&Yz!VgT2%^w zd?VRHI^@HqMtMAuBl2*$_Ysv6C@y^B3o7iRDgd=gwF)iyJ%2=b^_v~$7gg?!tz-crv3ZC~lNL4QX+ zSA=&Hvw%jtXwPZ+_U?0!Sa(5g?&JXz{^?rK6`nLtp~j9wtYG5uA~1)jfl|Mx&Q7&( z6a|*?61k_2bq51W%1shRd2XxIN~PJi%pT3>jJZAKGD`54vQc5W8#)ANwto52k3J2I z=&sOvUTIaSuhO}WC1$n4!AUscpmsp7qyklBC#cFtzr5-MY|8#h%C9 zT{Z`G4m&%W>p#dEzm3gTkGy4EPLC1d7U5JSzu0Yib4to3tRi%?f)o-HpVK&t)+}?i z_3J;MysOrjkXWmFhoyb!W1%EE68H>(R4&BFyQ-e~Vi){85zl$Ss^Ht2`e zSa`dS`_nx1wD6@<>tb+Pw^gER;-UZCxGO%fzeyO8lW5hgtQu|7NSL!8VzE3oJKb;S zwB6k6G^8;|=#~F@3vx%`HCjTC0ntf4B0Vk(yu4Z285IY%@-K@<2FOP;t3H|XR{y+` zgVUUy_m@~2CATEgUa}(0P{{00^&)o+sbMYj3!thMwrgUduiIZSk9g1r-Ah)b`6-{S zJ-yYBWWJr|$R*uJ{x#+d8v_e2Z%v$kA<`iFUGaSTvb6rB`hg5{47EY8wTlY-sDkEt zf|wuhwpnSelx(r7a6z@r#;@n5N$(jt26Mrwau>4@fuWg%VWfSO}m#t%6RdAJW zs3-|~2Dp1x-B9@3%6)qWi z0*Crm@rL)G($Cyu7lEbxpt)1hroTs!J-|l7nYW-~nz|3(7@>NkElZ76Zk~Hy_L3Li z;Me0@-uQXaLLzXvtmfBS8R@J0`T2f26gt9bTAF>%a|O3s3!MwZ?Y}{^vpMo=*x7RW zd#vo_0%mdXjz%fvNI^-#3{GZo10+?kL4z}d|NR>;rAt_1H~AuiDZ+> z^+M&aMu%!&J@UJ|4g3+09m^$^G`jT#(`8*!S+P}^4v(!GvI{t8N;3qeaL9t{zCV{% zAjL>krTLbFm(un@63s=^XWd+_);xOG=E2m@fC*-Rj=^S`-z&A1{fX<`iIh;S+|a^u zdRD67hWxa=!AA<`HX635?$)_Ty~UkYSaXOkzwh-s5Qco_C~f47$sOwc_`@GaJQ6kk z`00ull~iYwnVpCBP9r`KZ^o8^?<$nxq>5DzYqmr3k;cQ|GF-pq9kegW z1-^6TxB|YcEvWAM!=z;oQ92oQp2^5O< zHTHLREJi$6*>BPb`Y!E*7sYLsTa^D)d8BNHhJP@1FOJTSj#FW?58=TL@s}#8h_j6p zM<&&YBzf^av0f+@W)gh0<9dZfi(6>%nmN4d=VHg znv|=j$gB#iF%0dAo#&Kod{hXOR85~P(SN#_>Tshj)-+vkJLhopDKHB6a1nR~!g}5s z91@$Ay$JaNr6&L$VC)n7;}ZAy^u>d5;-XLbywyHlhII_36uz z%2Hwo9f>^v;6cO8iX#i)Vm1jL6!9j!@3jeMVvZ3%9nCI`@8G6r>uvM;cc1s<<;_6C z_^TrEi*ohn8KWt$IA4$V%K`pkv2~>xO8Bz+esxhI|K}%#D9}KP0&84#L1B{6GYO&h zu(72cy0~M;p=)#Sj9!i2IB2!G&cprl8I-aExo#Fcrw>>?S?vg*cZ+>Xx5A#E-l6cY zR$KDWZ?e6;=fywuP?f4V?$K3sgYQSLcBzfF4NOe)htP!1;@^&AovT)ZBtQO->uvWh@A+$K>AeZ=dp3<#7hF8DF05b ze%virzd3)xakiuG=bwXGjaJkcQ;J&_+kV+k+b+y7-A^AuJDCpTaZ_OgX$G*5Sxs!z z(1Vp0UX`@jG6|u@2Ehz~7?gfG?R53kI(Eu27RsU8hso6frQicUIkC{@D+I|ipe;a@p$;V&4t=L;sdzp{?gsZzuTKElw|*zd`@%Zx^z#b(&8ddR4%mx5hiLn@UOD z#fE}Npzdo+9oFre!o7RWxrLFGYCNVwsmlVmpjFCxvloU^lm8}H za2QFd5_#cu(=zSnhBMd6z@%s1zQ6mwxgZ6ZWw7*|@68g==UoFi$Op}#T=v`9;>9;SVYVL-?&4@e^@(Wz*(ZO&w7(JqP^Rp*H2sm~hk)Hr>bFI_q~o{A!@ za9r*xByy?e8`rOywKw^r6|-Ad$d1Bc)nzFws**+pAN!~-xWhb?$iEf(zz}~i{~H}^ zjlPWQYQw|e9pL>Y#h@o07rxq<2uextVA)^U@=*>li1$vBK=l5myl>q5srlYsxUX&Q zFPIJC$UA)D-{L?P!!{>{=HAqGk4$y4o+(+m#AYSS(L({4j3w7+I`*O!pvKX04g4fo6gKw)a{Cv7=$CRFRr>o;~ zP0HG~w5OlB#~bUXCXevKrc1F%CORW^QzeFiE8N9<{6)=42EJO`UiCkObhf=fZ^mD$ zP-^~WuY;js&`s)MU{YCMqJ53M7NXYtZgtn*6uZ%JONqOhmf{!w)^FJgq~x_*#2AgM zP(J5uG+(E0Nuf=hQa?612KlKW+ImvYy1|!@exPS?pxeG`=y1|Y$QR0;bv{B+94JSs zgu#WJ1`8^f01`&jg5tL;-;f?h19G(Px7q@B>!@A;<0^>D*<0d0Hqw@d=3uMau?Lrd zBmH%)*(bY2&snV?QN}`_60Hv+ghb%$8ZQSEDO1zfY{nPQ=}Bp2JR7BM?i0JY8Zmboxw-Pl1M@{8 zPdm_?Oj*~IVax@{ad-`loal9rKtr>cAIV5uK*t8{Q8s`AAh)=8QRw|kwLf?2niyTx Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mp2.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mp2.png new file mode 100644 index 0000000000000000000000000000000000000000..471bd7515c93f34a560b2534ae74b13391122443 GIT binary patch literal 6854 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 Fe*hM8nV0|o literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mp4.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mp4.png new file mode 100644 index 0000000000000000000000000000000000000000..9c033a2d0c412330bb4ebdc2d535f91e05a6e328 GIT binary patch literal 6080 zcmaJ_c{r49-<}zRk{bk*t3v7G||0Cbufss?8(`p*lY zI{TIooF~r~RyWKYH$$?Gn&wx z=;*;!$S!!elqf>P8X=8*5|Y=iU%v(yM<5Ym2yro_xCrt(N&#N%wR3ZGLWzladU}d_N{EtOY{ihWva)|P#KlF<5F)PLBsZ*=2+8&G z-wLXDS8Er7lN*6dg8xy(-Y2`eDe#^}`mYp-PXCc5x&FIMX9W}U!a9i|MG=2e`WvXD z^Z!GM#Q&gO-3;*mjrac)b~W;L!iyQ;UCHh))@O~gx%|hK6H3Jek98xv7?H`2e^*h@ zj_gKuwIe&hRSc!zI#_E0>5u&vUPlL|Npf|=lC1HXstUYk3Zeu84y7z5i;=~Mi%ZBz zD_iqjp_Z5~+%i`kSjtwst4tNp630asTB?{3G|zC=i{_Jgeee2p)Kx znhTi-|0`t_;h$qc{G;B#xwwCh1^JI$v9n;r{xtS~HTv(Pv+4P>{mW!I5LwU2$jR(thXsn944JF+B85)wd7czj%Z^I7j2wx>8iN zC556GpWJovtnKxl$Z}n;uaqdj84ek6uGralE;{#mPVQ$R!{|4$8CaI)tx*Zvo2?%L z!*ln8d==Fbixmg6sy7$Q79DHjsBX;N($P6U1qwN8h)f6&1CSD6BR%6`2j#2vG8!cHpJ0YE9f&AB;!M zuAH0YMAxA|;wU^KujZZPMyzgkLrk`?Mg}b{?K&L&R+vt2;1+MPaB^3uQou^_h9uk1 z{25n8U!QY|`yZhrDU>P15R9+(x=u{N#4spF;p1`3k@y@fE5E@>tPi>BiOg2n(b8`D zR`qZ^n~b98RD=KQsSd>^6&MXF-kk_MUK~!S$Pe7DQVLfU@0<dn zKcKz-k#Dap;3kpn87LTzSeoR@ikOq_TYDGZ+pb-8XdY-%+LQ=s(e|3<4Mr#_ZG7(T zUXA8Y<#rF*_)YaVHfe3oXWp*?Q2BdbrS@+4RuoNYdO8)1)%^mXNFdnhi!*FrVQim+ zUp?W+M}>QfwcDuy0)Rmw{jl}}0f*$a-kHT?nJ|z6hcd#00^myxu*-2Yn3&J_w7DH4 zme+pHc)qswQ@pm@bk{7E_PBV}2YeV}bQzx?$P1ir-qYH z+w!M*rolcf9<+g{u+v{3vXxF`R?{Y^uc{iC->scppK7$6aG;wmGm2E{(w!Wtjht5U zzjo*gd?9rt<_F!bS&h#<0)nP#sP5z4>o>(tkLm?Zc6+%ZDx&-)=_CAFes45xaLG8K zCtLwI3&2v9HhoOT!A|=jo68Iu`NXJ%u4__t-2evtai%c|-ZdMEi^qHZ@$HshS z4r~K{J+V9qRodDv?2Pkx7z&z+1Hcsr(m4E1v@Mg79Mq@ld^PH-ARJtr5R#U<~+C+LQe~ zR;O|45o9~j_HE_%kBu)wF9R<`I75mx`e(N1Keeyhqctw>^3@E7+6EqlzWUlK@aU05 zr8w|)Ub+?A_%|x+SMKKD*}Nhna~_mi=dxaIzZsbOtHhqnDtTlXxd<$){Tvwi^xjaUfY9f-r4Ugf}%o)HU zZj9y-Bv|;IQTqq3J4f>OJ@O~KpjSusqt+Ue=qA$i)Tot=l*Ya8P8KBRm2`{eow%3mv&>@)TT2>kgcuxm3tG`*Rf428(>=$pkgq}3ws zI@g>d8# zJQ&RO%O57}$3691BBY!8Y}D^hG?a4b@iNkcx=~OheOL{`77u{KCazyMU+U(-y@g!1 z_O-(Gvdl%qK%v1YB5`Q(%wVOXBP12pnLx3Uk*P5TRX(PlkI*6wCsy~{R~d>O&5n|J z;FyB9>7Q6$z~Jc$hdD-`s=2S-ZWq=V!@aBhk1`)WH!h_#wP9x99L8tnDiCAMG8=C_ z6YqODJQMt#VY9EZ-VYYMZ%35Q)#F`UELJ^tP;eJcYK8^4G0!e*x_#W&Yz!VgT2%^w zd?VRHI^@HqMtMAuBl2*$_Ysv6C@y^B3o7iRDgd=gwF)iyJ%2=b^_v~$7gg?!tz-crv3ZC~lNL4QX+ zSA=&Hvw%jtXwPZ+_U?0!Sa(5g?&JXz{^?rK6`nLtp~j9wtYG5uA~1)jfl|Mx&Q7&( z6a|*?61k_2bq51W%1shRd2XxIN~PJi%pT3>jJZAKGD`54vQc5W8#)ANwto52k3J2I z=&sOvUTIaSuhO}WC1$n4!AUscpmsp7qyklBC#cFtzr5-MY|8#h%C9 zT{Z`G4m&%W>p#dEzm3gTkGy4EPLC1d7U5JSzu0Yib4to3tRi%?f)o-HpVK&t)+}?i z_3J;MysOrjkXWmFhoyb!W1%EE68H>(R4&BFyQ-e~Vi){85zl$Ss^Ht2`e zSa`dS`_nx1wD6@<>tb+Pw^gER;-UZCxGO%fzeyO8lW5hgtQu|7NSL!8VzE3oJKb;S zwB6k6G^8;|=#~F@3vx%`HCjTC0ntf4B0Vk(yu4Z285IY%@-K@<2FOP;t3H|XR{y+` zgVUUy_m@~2CATEgUa}(0P{{00^&)o+sbMYj3!thMwrgUduiIZSk9g1r-Ah)b`6-{S zJ-yYBWWJr|$R*uJ{x#+d8v_e2Z%v$kA<`iFUGaSTvb6rB`hg5{47EY8wTlY-sDkEt zf|wuhwpnSelx(r7a6z@r#;@n5N$(jt26Mrwau>4@fuWg%VWfSO}m#t%6RdAJW zs3-|~2Dp1x-B9@3%6)qWi z0*Crm@rL)G($Cyu7lEbxpt)1hroTs!J-|l7nYW-~nz|3(7@>NkElZ76Zk~Hy_L3Li z;Me0@-uQXaLLzXvtmfBS8R@J0`T2f26gt9bTAF>%a|O3s3!MwZ?Y}{^vpMo=*x7RW zd#vo_0%mdXjz%fvNI^-#3{GZo10+?kL4z}d|NR>;rAt_1H~AuiDZ+> z^+M&aMu%!&J@UJ|4g3+09m^$^G`jT#(`8*!S+P}^4v(!GvI{t8N;3qeaL9t{zCV{% zAjL>krTLbFm(un@63s=^XWd+_);xOG=E2m@fC*-Rj=^S`-z&A1{fX<`iIh;S+|a^u zdRD67hWxa=!AA<`HX635?$)_Ty~UkYSaXOkzwh-s5Qco_C~f47$sOwc_`@GaJQ6kk z`00ull~iYwnVpCBP9r`KZ^o8^?<$nxq>5DzYqmr3k;cQ|GF-pq9kegW z1-^6TxB|YcEvWAM!=z;oQ92oQp2^5O< zHTHLREJi$6*>BPb`Y!E*7sYLsTa^D)d8BNHhJP@1FOJTSj#FW?58=TL@s}#8h_j6p zM<&&YBzf^av0f+@W)gh0<9dZfi(6>%nmN4d=VHg znv|=j$gB#iF%0dAo#&Kod{hXOR85~P(SN#_>Tshj)-+vkJLhopDKHB6a1nR~!g}5s z91@$Ay$JaNr6&L$VC)n7;}ZAy^u>d5;-XLbywyHlhII_36uz z%2Hwo9f>^v;6cO8iX#i)Vm1jL6!9j!@3jeMVvZ3%9nCI`@8G6r>uvM;cc1s<<;_6C z_^TrEi*ohn8KWt$IA4$V%K`pkv2~>xO8Bz+esxhI|K}%#D9}KP0&84#L1B{6GYO&h zu(72cy0~M;p=)#Sj9!i2IB2!G&cprl8I-aExo#Fcrw>>?S?vg*cZ+>Xx5A#E-l6cY zR$KDWZ?e6;=fywuP?f4V?$K3sgYQSLcBzfF4NOe)htP!1;@^&AovT)ZBtQO->uvWh@A+$K>AeZ=dp3<#7hF8DF05b ze%virzd3)xakiuG=bwXGjaJkcQ;J&_+kV+k+b+y7-A^AuJDCpTaZ_OgX$G*5Sxs!z z(1Vp0UX`@jG6|u@2Ehz~7?gfG?R53kI(Eu27RsU8hso6frQicUIkC{@D+I|ipe;a@p$;V&4t=L;sdzp{?gsZzuTKElw|*zd`@%Zx^z#b(&8ddR4%mx5hiLn@UOD z#fE}Npzdo+9oFre!o7RWxrLFGYCNVwsmlVmpjFCxvloU^lm8}H za2QFd5_#cu(=zSnhBMd6z@%s1zQ6mwxgZ6ZWw7*|@68g==UoFi$Op}#T=v`9;>9;SVYVL-?&4@e^@(Wz*(ZO&w7(JqP^Rp*H2sm~hk)Hr>bFI_q~o{A!@ za9r*xByy?e8`rOywKw^r6|-Ad$d1Bc)nzFws**+pAN!~-xWhb?$iEf(zz}~i{~H}^ zjlPWQYQw|e9pL>Y#h@o07rxq<2uextVA)^U@=*>li1$vBK=l5myl>q5srlYsxUX&Q zFPIJC$UA)D-{L?P!!{>{=HAqGk4$y4o+(+m#AYSS(L({4j3w7+I`*O!pvKX04g4fo6gKw)a{Cv7=$CRFRr>o;~ zP0HG~w5OlB#~bUXCXevKrc1F%CORW^QzeFiE8N9<{6)=42EJO`UiCkObhf=fZ^mD$ zP-^~WuY;js&`s)MU{YCMqJ53M7NXYtZgtn*6uZ%JONqOhmf{!w)^FJgq~x_*#2AgM zP(J5uG+(E0Nuf=hQa?612KlKW+ImvYy1|!@exPS?pxeG`=y1|Y$QR0;bv{B+94JSs zgu#WJ1`8^f01`&jg5tL;-;f?h19G(Px7q@B>!@A;<0^>D*<0d0Hqw@d=3uMau?Lrd zBmH%)*(bY2&snV?QN}`_60Hv+ghb%$8ZQSEDO1zfY{nPQ=}Bp2JR7BM?i0JY8Zmboxw-Pl1M@{8 zPdm_?Oj*~IVax@{ad-`loal9rKtr>cAIV5uK*t8{Q8s`AAh)=8QRw|kwLf?2niyTx Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mpeg.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mpeg.png new file mode 100644 index 0000000000000000000000000000000000000000..9c033a2d0c412330bb4ebdc2d535f91e05a6e328 GIT binary patch literal 6080 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mpeg2.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mpeg2.png new file mode 100644 index 0000000000000000000000000000000000000000..9c033a2d0c412330bb4ebdc2d535f91e05a6e328 GIT binary patch literal 6080 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mpv2.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_mpv2.png new file mode 100644 index 0000000000000000000000000000000000000000..9c033a2d0c412330bb4ebdc2d535f91e05a6e328 GIT binary patch literal 6080 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_numbers.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_numbers.png new file mode 100644 index 0000000000000000000000000000000000000000..cba9af2bb9fdb4804b7a57d2b6f67addef4afe46 GIT binary patch literal 5476 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 literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_odg.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_odg.png new file mode 100644 index 0000000000000000000000000000000000000000..1f31e190b005f192374d94bff91c4a19dc20121d GIT binary patch literal 5950 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_ogg.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_ogg.png new file mode 100644 index 0000000000000000000000000000000000000000..471bd7515c93f34a560b2534ae74b13391122443 GIT binary patch literal 6854 zcmaKRbyQp5)@_0niWhe)Zh_zyym*n|QY<(L5F}8%xNEWE)&hk>u^Ljmg#yJL3WY*( ziiH4=-tYeI_x^bIoiWbYGUl9X?X}k!>+Eyl_4OVR->1J1004+JHB=4nTE)LNKHlB4 z#?^cIuA%c)GxIe~5jtF}gtEQ)&Gt3ZX=NRZc1d{^*u-RRqX1->+Ix-Fj zPa(U%7$KCW*Bu)Gkb|JS>>S)-zO42zXIHp9`@x4!c2-wMd3IBAU68Jq3e3e-BghA4 z6r=}r2y%Cjc4UVru*#ui?gTtxzILoAPY*a!1|`q_FI}0t{@-a~cGiDEeBI^Q|Hmma zU42#+gb$2WTnHrS0FnT)N{9%FfW;&wB?VYTK_Vbwkf^YTsGx|X3|LeK1Y-U7#eQea z$I(f~P*wfkw(dsq>@L2(UNXYM0RaI*0bn77kF&6dw6yeJ4N+0SJA@!I5bkS-5`-f; z{!vhcAsu{Ny?k8}aMr(ycJ>HAUwQUBPyc5Lo?icvg(LqRrn`U%qwKtdMT9_qm-G)% zSNH!9_4ND?8tH2Y`(JW8=>OKfh)_*No#`VAZ0{*Xh|K>XWcVEQ*D_8i=8R5Sh`+sfpKT~(v^SAq-w7nbr zC-GtMyKMKlOY8SRJPZH;jf|$M5)?H*Y)KHDJ(UeXe0^$ebCyQd6iEO~WXJi!Ck3IR zbccnmTku;{v`m|183+_wc+c;=q)PZO#S2yCe+Kg-!Y3#gGhwSEAk)AHHf6{oG?$+u zZ?-Hij!^_S_SMDA`)!K{9Z$c>Z-qTS=%>WT;kf|i=}b;EA_qt)WDe~lYHxkC7r#8E z&zo{blAb@ia_RiKm77+~n^sh`UmYr=e8k&1M+3w^Q8n3LT~$~cM$v-dCL80j$WojK zm?Yk=E!+8#4g2FmOfVfCT?9f4Fs(omd2=KX-8|dgPEHcbq5aAEdvrg9^4HqHxZB40 z)zH?7CjWW+Pb3^;EjK?D6&2NRnf+ErFgrVY`G)K>m220T=YkhFgV%X5jScyO;z4NA z(E3=OKHY=`hwE^exS^?Oj&fn)VtcwL- zQNUk8*J+bQ>{U%-#JJn%vHj?*R;4%jbiN-(JuTh&=R8`ynAd572l}C1P!cvM6pCtU zYC;=R2w4L{J7*!c6n#4Ok@_ro&FS9dtVv0D;G`*1>ZE3!kLdPZ#IUKj28A*QVfR9@ zGU!RueWjM`Q1IjaDsa!n5z1wvD!G?}qhW@huWo#M!nkbNeY);Bazt)9>{ebT^rKD` zPLr8{ncsav!g7a!lz0Ht=kWRx6HMQkGcpm^&z_Z&Q*g6}6^`d}=3zA#6N6{b)9DKlfz3KN2e;A*t-MVD#kpI9o_f?dbO+`KOLAA|prr*xHxi ze1d}Ho8HOKaaZ3g?44XPc#(Ep_+( zX##VHp7r$|V~RfPZ<>h3;#F3REqC4~FER`F4fIbWSvn8a94xe@It{n$_BYhlIuJ|o z4KqX};%?Xm<>NZq0qbW#YZpiBHh7M=5LJM>Y1NznfL~lX`X*#Q)3Gf!ePD^L9nfN` z-=Teum7mj<4wfo7djk?MVJHvFNue1gS_-?xM*F3a?UIv|KR)*3CM+%KjPf$44=>*s zW2=FGFlu{ZtM2U{Owv~UUICH|!UxGlVla()Ai+&Oe)Mt(D2`%!jIzFZ#f)Bik z-|MWef9^TU#U?ro7^QeXF-Z5bVz>JluY!V26aibIjJc96pClsJ{uad@1%Lt*$h_>y zlsTSNOLB8_A133kwx}f*)k=qKVn0%i;vD;FnMO%7z(3m;KF!N-W9UO0k)Z>Uk;z9z zW{KGXqUSzm(MI5o4TV_4X0?6MHBMHjeR89GLWPiR>?efO+C zh3V`32HD6_qU#INB%K=STw<(nb094T2S@SRiNjmZbm}R`BO6&PB=$*lZOyN@#0-@BUx!LJvLe{SU&etF@Cnnv--o+gxQpA8 z1?L`&SLfb^eP^Ow?a1skWeoO*SGGHjSP<*Xg zBbS!enx5;A8a~ZLxY|d0QgLx{-4T{d2qFAy54`Tvr$Pm^3oa+uLqm0SuF9%P1LeBR zjCHIm+Jh3Wems`0R9q^2RXg6mAO2dJ39G1;$H{^cGO)IUDo~RV?5F1ZJ0$ndR63+m z@2pY1{UFa?9YHD*(l##3DDBfL>-$Up7Dl*w+Ly=@wRGdLoT>J8IjmDI-IT_lc;%oR zB50{aa72)mU8uV)5MCj54^}-dX875Uum42 zx<<4MvH`{%C0B&Ny*t#6ggieG`h=7 zJoPBtZg3?cyrsFh`8USUDgmG)7JX|zC?*b;)eg+w?z#!FKk;Vl?FF~?^+~__WNgP+ zL%P|GLe>^&=}l&S{R;97(65`&+a6k@v@>n$1XPXYkBxUTe|BSY){*1njxz8Jp;T*D z{m#%JSbDH5Vr0efz8^gM*-zPpJU#upiE&{W5Ic*fIYHhK{3y+VR@E+Om+nF>DB)R+ z^E@kjHYX5KTM=kMU~u|eu)RtCX#n$MGDHJ1gc6GaR;P$_BiNDpCIzF-XdgjdfR)3ALsFaDu3JKS!|jKzD5&Z)u2QZpyBM8>zIuq<*TfjelKUio|_jg0;l==)s9Yq!|-TE-~rn zk1LZ8PguAWyB++H^o}ODq-?q`Kn7x~T@lypyW{1LChE*d$Hq$E`JOMGe5al?&~xrIJ}J60(-3GW25S?dGi=>t-%(sZ;~ppR8+n zv8xKkODluc@XJC-xE3q9EPzvL`6JZyg~g8<$21N`3M9XNIOw=a0i}A;Tcj65O!~Wa z+&7He$wQ_Aq0)q&y1Kd)>k&FQm$H*|JiEsiR3o-Z=6P}ztEze?qQtgrlM>AdfRcPB zkJ>Sw+=L~kvTj$EKan+cP4sN`0yJB6W6Ur8w6qcoY1b4amrxY(^$W<*t}*ZrZu;{bjgbG-=>vU7HurQun&#(Sg*%)kaX7+7Uvgyd1_zkOZy! zwO5;E)4%RM@zWO2Fi3Bccd&o+9_8nKw9J*5aPvkj>x03Wg3-Xv&grXo-KcQVHx%>s zc6Sx6H2zX0O7vdeiF{_7A(kl)V3TS0d?}Q>(A}e4{TueTql-W{*I_)MRN)^8KcrO9 zvNg`NJ#S3H%LXeOf;ue8+LX_D@*<#neul3z|M@vF&g*IRqAuvqg4OA%`!CJ5)mzoF zTHM@g0?TxLe^6dIGnnve4b~8LSBQE{vi-gC^PijRtL?E;mG`of9WsgMpx}btOaT1T-ZWQ{ z;!!of>5_q=56W)s`3RPVe&0dx)(5C{In<}-w>8vwz0AuAXZ#6e?4Z6;JPnl1%+HR; zaUF2~bAnI5zL_dxw(%wEXTkPv9vEtEJ>sIE-aJ%umY5Pxtxguf{@$t$UPx&J48M4t z%mB%;7wC9Qdq&GMRr!@Fyat!hv-QmYX%=(s>>Imldkq%1`0vJWIJ1bv*ej!K-?EzQ z6w_7%GZZBu&vS|bDRL;z)|G!ObxZV+BS+p{A*f5^luQ5e?P9JX|L0q}e)_JsqObMD zbX-<;)2rQ+fV1L4`65_d)6qLN2I zonnzspWRw08!mlM7gg?&3RqnZ4fI*W1#Vdf5GWSb@l+=q`%o)VF!4CdY>CDL%GMw7 z#PFKTqonVxb;$GO(Jy*KEtZx1T8N?M=G_i2R+EGy;6)O;ICwJ!a-eLZ1J&siq!`9> zC1dJd$idU|M1Dp|_=^k#Xm9?M^V9J$zhSb({WU{S$;>qnx0tFI9c&#T=e(Pw~c(-&!rVbdz4;}--J zlr3Ag9gfCD3hNSf^j&yB+u_sSGx4?vMZbR!$4~l0|1ajF>$)yT5zq= zdj-oisQ|sbuv#_30HJ*@i6PVW(^!6Ehl5^!8b$^TzFJtR5_w>|}kZpp*|X_RA@ahyO(uNdq9j%*Lja z+w1)$ee2$z*1dxaNzIWEj5kL4!NjCmB2B~CK8JwKS^gZm{!Xp&!Mv0T2_+~diiXQZ9pXg zEGYPT!QOU8)3-S@hO|>=Lv}2U+9qz*Q04*egjz;vhe!H5fc~5O4*>^Nu`ES`m%v5y zVEYHj#bSB(v$4XK9WVM*iWS`@JxcO!{i}m-@Ul&z_+D#F;dB+(eeN}eUrz@oAo00eA%*ef6R7UcxwtBH(h5QV8HfSuLZDe zNb)Du#2r}LMZOuC07A1;QS11~oGP${wp*p*NJn*ulKqK}t8UDA>pWWJ%GmE#JGr z8J4;(01aO>Vy^zI^)A#ES7TSD=w#K$Wt>NNTd&Lh^wzX@wc9-?-lY_!;uUB%MQ32y+ zrmlCd1Q|O*=1sYoFQku!(B;A&bRmtI(BGtN4q&c*j$*|`rb-m)=oe{DQl}e3T(3SG zfQXD03l~m66d858t&B{b15}_E>^-W>G^3>}mJ7s=UE89y2;NHo78FF>6h%{xK*(jZ&I95TSubnaZ;F< z6}G!F0U)Y`;Do?NY>)6vaub(Y2tzeBJ*}Bbco^xv@OI0+(c$IK-wnd0vCKigg5zJW zB7ya~1rpo*RZ+dwr@=pJ8*YT6u=7|!RZ(EwsOkAe`@Yz%^2yjR)|2)ZQmYA{6hlKh z+65*r(g4ZK^6G9_sS0RXZXWkv$&$;v&k0>L5OPIfH;@m}pUk|I60KJ6DhbW!M!(O{rB{OzXfgPofI&Vm-ndPJac*+dqnrG zg|?G>Mi#lLBOS{yGCWKM#EJcu3%;RF57LZB@8DvHY6{hXa_C==(SofrB#r@}m}aac zwIIc!uo`_*f=o%T^bEr%;w6ck^Zl*AKNq)-LypFmmu>o-7RulmL{=jBcv(F?&!#D< z;pXVkVL9RFiQN|=!&Jz9T|)Gqww_EbT~&4#Qi{&C7zTAmXJ-e}M&rw`b0-V1NJ((P zpzqoWX}%5{IvOR}E^Lr`P%yVFXyU$={{6eku5zkKT+!Noa%b?_Tz%-}f?X~kLvi<` zfPjFUF%(J})5+3iCcw3qSa{bk*t3v7G||0Cbufss?8(`p*lY zI{TIooF~r~RyWKYH$$?Gn&wx z=;*;!$S!!elqf>P8X=8*5|Y=iU%v(yM<5Ym2yro_xCrt(N&#N%wR3ZGLWzladU}d_N{EtOY{ihWva)|P#KlF<5F)PLBsZ*=2+8&G z-wLXDS8Er7lN*6dg8xy(-Y2`eDe#^}`mYp-PXCc5x&FIMX9W}U!a9i|MG=2e`WvXD z^Z!GM#Q&gO-3;*mjrac)b~W;L!iyQ;UCHh))@O~gx%|hK6H3Jek98xv7?H`2e^*h@ zj_gKuwIe&hRSc!zI#_E0>5u&vUPlL|Npf|=lC1HXstUYk3Zeu84y7z5i;=~Mi%ZBz zD_iqjp_Z5~+%i`kSjtwst4tNp630asTB?{3G|zC=i{_Jgeee2p)Kx znhTi-|0`t_;h$qc{G;B#xwwCh1^JI$v9n;r{xtS~HTv(Pv+4P>{mW!I5LwU2$jR(thXsn944JF+B85)wd7czj%Z^I7j2wx>8iN zC556GpWJovtnKxl$Z}n;uaqdj84ek6uGralE;{#mPVQ$R!{|4$8CaI)tx*Zvo2?%L z!*ln8d==Fbixmg6sy7$Q79DHjsBX;N($P6U1qwN8h)f6&1CSD6BR%6`2j#2vG8!cHpJ0YE9f&AB;!M zuAH0YMAxA|;wU^KujZZPMyzgkLrk`?Mg}b{?K&L&R+vt2;1+MPaB^3uQou^_h9uk1 z{25n8U!QY|`yZhrDU>P15R9+(x=u{N#4spF;p1`3k@y@fE5E@>tPi>BiOg2n(b8`D zR`qZ^n~b98RD=KQsSd>^6&MXF-kk_MUK~!S$Pe7DQVLfU@0<dn zKcKz-k#Dap;3kpn87LTzSeoR@ikOq_TYDGZ+pb-8XdY-%+LQ=s(e|3<4Mr#_ZG7(T zUXA8Y<#rF*_)YaVHfe3oXWp*?Q2BdbrS@+4RuoNYdO8)1)%^mXNFdnhi!*FrVQim+ zUp?W+M}>QfwcDuy0)Rmw{jl}}0f*$a-kHT?nJ|z6hcd#00^myxu*-2Yn3&J_w7DH4 zme+pHc)qswQ@pm@bk{7E_PBV}2YeV}bQzx?$P1ir-qYH z+w!M*rolcf9<+g{u+v{3vXxF`R?{Y^uc{iC->scppK7$6aG;wmGm2E{(w!Wtjht5U zzjo*gd?9rt<_F!bS&h#<0)nP#sP5z4>o>(tkLm?Zc6+%ZDx&-)=_CAFes45xaLG8K zCtLwI3&2v9HhoOT!A|=jo68Iu`NXJ%u4__t-2evtai%c|-ZdMEi^qHZ@$HshS z4r~K{J+V9qRodDv?2Pkx7z&z+1Hcsr(m4E1v@Mg79Mq@ld^PH-ARJtr5R#U<~+C+LQe~ zR;O|45o9~j_HE_%kBu)wF9R<`I75mx`e(N1Keeyhqctw>^3@E7+6EqlzWUlK@aU05 zr8w|)Ub+?A_%|x+SMKKD*}Nhna~_mi=dxaIzZsbOtHhqnDtTlXxd<$){Tvwi^xjaUfY9f-r4Ugf}%o)HU zZj9y-Bv|;IQTqq3J4f>OJ@O~KpjSusqt+Ue=qA$i)Tot=l*Ya8P8KBRm2`{eow%3mv&>@)TT2>kgcuxm3tG`*Rf428(>=$pkgq}3ws zI@g>d8# zJQ&RO%O57}$3691BBY!8Y}D^hG?a4b@iNkcx=~OheOL{`77u{KCazyMU+U(-y@g!1 z_O-(Gvdl%qK%v1YB5`Q(%wVOXBP12pnLx3Uk*P5TRX(PlkI*6wCsy~{R~d>O&5n|J z;FyB9>7Q6$z~Jc$hdD-`s=2S-ZWq=V!@aBhk1`)WH!h_#wP9x99L8tnDiCAMG8=C_ z6YqODJQMt#VY9EZ-VYYMZ%35Q)#F`UELJ^tP;eJcYK8^4G0!e*x_#W&Yz!VgT2%^w zd?VRHI^@HqMtMAuBl2*$_Ysv6C@y^B3o7iRDgd=gwF)iyJ%2=b^_v~$7gg?!tz-crv3ZC~lNL4QX+ zSA=&Hvw%jtXwPZ+_U?0!Sa(5g?&JXz{^?rK6`nLtp~j9wtYG5uA~1)jfl|Mx&Q7&( z6a|*?61k_2bq51W%1shRd2XxIN~PJi%pT3>jJZAKGD`54vQc5W8#)ANwto52k3J2I z=&sOvUTIaSuhO}WC1$n4!AUscpmsp7qyklBC#cFtzr5-MY|8#h%C9 zT{Z`G4m&%W>p#dEzm3gTkGy4EPLC1d7U5JSzu0Yib4to3tRi%?f)o-HpVK&t)+}?i z_3J;MysOrjkXWmFhoyb!W1%EE68H>(R4&BFyQ-e~Vi){85zl$Ss^Ht2`e zSa`dS`_nx1wD6@<>tb+Pw^gER;-UZCxGO%fzeyO8lW5hgtQu|7NSL!8VzE3oJKb;S zwB6k6G^8;|=#~F@3vx%`HCjTC0ntf4B0Vk(yu4Z285IY%@-K@<2FOP;t3H|XR{y+` zgVUUy_m@~2CATEgUa}(0P{{00^&)o+sbMYj3!thMwrgUduiIZSk9g1r-Ah)b`6-{S zJ-yYBWWJr|$R*uJ{x#+d8v_e2Z%v$kA<`iFUGaSTvb6rB`hg5{47EY8wTlY-sDkEt zf|wuhwpnSelx(r7a6z@r#;@n5N$(jt26Mrwau>4@fuWg%VWfSO}m#t%6RdAJW zs3-|~2Dp1x-B9@3%6)qWi z0*Crm@rL)G($Cyu7lEbxpt)1hroTs!J-|l7nYW-~nz|3(7@>NkElZ76Zk~Hy_L3Li z;Me0@-uQXaLLzXvtmfBS8R@J0`T2f26gt9bTAF>%a|O3s3!MwZ?Y}{^vpMo=*x7RW zd#vo_0%mdXjz%fvNI^-#3{GZo10+?kL4z}d|NR>;rAt_1H~AuiDZ+> z^+M&aMu%!&J@UJ|4g3+09m^$^G`jT#(`8*!S+P}^4v(!GvI{t8N;3qeaL9t{zCV{% zAjL>krTLbFm(un@63s=^XWd+_);xOG=E2m@fC*-Rj=^S`-z&A1{fX<`iIh;S+|a^u zdRD67hWxa=!AA<`HX635?$)_Ty~UkYSaXOkzwh-s5Qco_C~f47$sOwc_`@GaJQ6kk z`00ull~iYwnVpCBP9r`KZ^o8^?<$nxq>5DzYqmr3k;cQ|GF-pq9kegW z1-^6TxB|YcEvWAM!=z;oQ92oQp2^5O< zHTHLREJi$6*>BPb`Y!E*7sYLsTa^D)d8BNHhJP@1FOJTSj#FW?58=TL@s}#8h_j6p zM<&&YBzf^av0f+@W)gh0<9dZfi(6>%nmN4d=VHg znv|=j$gB#iF%0dAo#&Kod{hXOR85~P(SN#_>Tshj)-+vkJLhopDKHB6a1nR~!g}5s z91@$Ay$JaNr6&L$VC)n7;}ZAy^u>d5;-XLbywyHlhII_36uz z%2Hwo9f>^v;6cO8iX#i)Vm1jL6!9j!@3jeMVvZ3%9nCI`@8G6r>uvM;cc1s<<;_6C z_^TrEi*ohn8KWt$IA4$V%K`pkv2~>xO8Bz+esxhI|K}%#D9}KP0&84#L1B{6GYO&h zu(72cy0~M;p=)#Sj9!i2IB2!G&cprl8I-aExo#Fcrw>>?S?vg*cZ+>Xx5A#E-l6cY zR$KDWZ?e6;=fywuP?f4V?$K3sgYQSLcBzfF4NOe)htP!1;@^&AovT)ZBtQO->uvWh@A+$K>AeZ=dp3<#7hF8DF05b ze%virzd3)xakiuG=bwXGjaJkcQ;J&_+kV+k+b+y7-A^AuJDCpTaZ_OgX$G*5Sxs!z z(1Vp0UX`@jG6|u@2Ehz~7?gfG?R53kI(Eu27RsU8hso6frQicUIkC{@D+I|ipe;a@p$;V&4t=L;sdzp{?gsZzuTKElw|*zd`@%Zx^z#b(&8ddR4%mx5hiLn@UOD z#fE}Npzdo+9oFre!o7RWxrLFGYCNVwsmlVmpjFCxvloU^lm8}H za2QFd5_#cu(=zSnhBMd6z@%s1zQ6mwxgZ6ZWw7*|@68g==UoFi$Op}#T=v`9;>9;SVYVL-?&4@e^@(Wz*(ZO&w7(JqP^Rp*H2sm~hk)Hr>bFI_q~o{A!@ za9r*xByy?e8`rOywKw^r6|-Ad$d1Bc)nzFws**+pAN!~-xWhb?$iEf(zz}~i{~H}^ zjlPWQYQw|e9pL>Y#h@o07rxq<2uextVA)^U@=*>li1$vBK=l5myl>q5srlYsxUX&Q zFPIJC$UA)D-{L?P!!{>{=HAqGk4$y4o+(+m#AYSS(L({4j3w7+I`*O!pvKX04g4fo6gKw)a{Cv7=$CRFRr>o;~ zP0HG~w5OlB#~bUXCXevKrc1F%CORW^QzeFiE8N9<{6)=42EJO`UiCkObhf=fZ^mD$ zP-^~WuY;js&`s)MU{YCMqJ53M7NXYtZgtn*6uZ%JONqOhmf{!w)^FJgq~x_*#2AgM zP(J5uG+(E0Nuf=hQa?612KlKW+ImvYy1|!@exPS?pxeG`=y1|Y$QR0;bv{B+94JSs zgu#WJ1`8^f01`&jg5tL;-;f?h19G(Px7q@B>!@A;<0^>D*<0d0Hqw@d=3uMau?Lrd zBmH%)*(bY2&snV?QN}`_60Hv+ghb%$8ZQSEDO1zfY{nPQ=}Bp2JR7BM?i0JY8Zmboxw-Pl1M@{8 zPdm_?Oj*~IVax@{ad-`loal9rKtr>cAIV5uK*t8{Q8s`AAh)=8QRw|kwLf?2niyTx Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_pages.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_pages.png new file mode 100644 index 0000000000000000000000000000000000000000..88fd2bba6d1e5030480d8dd9fb280796b9b6a0c0 GIT binary patch literal 5952 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 literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_pdf.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..f3fa1b6c88db8f07d2004ba0a22f1ca3ea64c913 GIT binary patch literal 5480 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 UDHUG-^AlP}!$7@S)jI5d032$d+yDRo literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_ppj.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_ppj.png new file mode 100644 index 0000000000000000000000000000000000000000..fb6d8b0857edfbe9c81de264c7d7004e702846ab GIT binary patch literal 5794 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 B;jjPz literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_pptx.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_pptx.png new file mode 100644 index 0000000000000000000000000000000000000000..7759087f34da9f78c67018617e8c356fdd1f53c8 GIT binary patch literal 5072 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 B;jjPz literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_psd.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_psd.png new file mode 100644 index 0000000000000000000000000000000000000000..4da78aad047e2e108168803526763d4e7dcae4fb GIT binary patch literal 5770 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_snd.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_snd.png new file mode 100644 index 0000000000000000000000000000000000000000..471bd7515c93f34a560b2534ae74b13391122443 GIT binary patch literal 6854 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 UDHUG-^AlP}!$7@S)jI5d032$d+yDRo literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_swf.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_swf.png new file mode 100644 index 0000000000000000000000000000000000000000..b02d696de48a441e6ba278d7224c43c7b59759ef GIT binary patch literal 5142 zcmaJ_XH-*JyFQ5sK|n)C1PM(-Pbh*wAe7Kcs8WRhi9|{eLYI-=i}WhJH>n~hy$FaD zQJPATst8zVDqNgzX1@F5&OK|Lv-i8#^E_`|`^SmK>TA%QV?PG~0G*bmn$hWs{{2EI zPrp^f+Y6`5MKZ>mY)m4MeQ+LlK-rOGkB4cw;%?%N@Hj`m+rxMT064=#G%+We>*>ll zkX*%ZzcFIIuI{I708mi!b;mh4+0g>Dd&sg`BPW!bpQKUoCo$NgzSvs`A<;h zdRUk$$pa6Q7DI?SAg&@{SCL{!Nhui_84;KS0*MeuNQfgPM3FLbk`i(V1njSe=hT~r zBSFqcP5m!lrxuFGiA;8v6BqaK@e%Wp6eD@u6h~gWcI~%@goNlRLe$gGjg0dZb@Sx? zTR{!)>EJ*NM<#igkVr0nR}t$( zB9lCwNbWFIV`-Qk&VlIm+x`=;rzfZ7=1Io6IpDR_P&}s!Vnm{&97bAM3L~v5Eh&3d z0*S;Rt|?;>7!_$*b(w2YNHv7?-&{44gO@Abjr=#)@xR=w|H}P63a;*_p4IRkL~p#K zx(CS>_Gij+#D9-P@?Z7-;yV6&EK>i<6+aC|{C8vjSEK(vI-Q>1+keLP)c9xc@ouNn z?r}QS&ke560RS{cOHJ9tcmAan(Z%`|vmz<$`JV6K?I+>79Iv%Bsrhg^hLF;;g(}8a z9l_=h?Qc|8?Z>ct;6faIQTGKC_&um;g-I3MC@Dk*OALnxmx=IS;=9f8w1G3=WB;B@ z-o^3JfCn3Ie0IOy|2%s+GApceb+glT;qiw2qQy?fcnsbGF;xbSX*%f{yI)oG?kKIdHu%QBNaH^`Qlfy%hsanwi00XmOLI?Y^}R4txi-0Ki>7dZCB96X}c>>=I!Gn zoMwmC0t&t}F>wwgc2r!Cp{g&kgOm;^3fj;@IGF_s3#wLhz^b7c=ida*#4fx03VL%# zy{n^u_(ubfdNN;%*v+#=ZXzrWQm-%R_x2n^Xw?Q`aUspY=|Qa7&K>m%QdBCSjBTD? z>2q(w_|kM$=#c5b(9V*(Z*9i=Xmakpot>SHzNwMODW6Vsc5WJz0<>|XP#E9SWN7T* zQs~NU-*v9Y?p35=k4xD~5+R8#Di`!Bo4VRE^wWy4IFN5u9EpfcR4ZP3P>UZY(_ab_ zPzB=(PI`|6cKlgxIKnu3n|N((Y})qryti~E?Ovp7mj->R$d_l;=ZsNSpKMrU1ngKs zU&XTH4i%J|rw_YF@!VNs-8o!b!2=tk;lg6(LwTsHS1JGK%16Qyp0*DkUO__`AwG)E z$IGR*SEZ!`m4iQTP8PQ%B%I%>0YMW78v3qKA~LpDoZ8auq;sSAdUm~fTy1S3O6{@~ zih2#a3PJ{kiaEo*BjI!i27{iVPw=>#FKB_i%W9H2!}WKZ^}x8LUJwQM{u>&f@f?{4 z)zC1RR#%!Ecl+p)VoG{ANgWvUbLBRtwUM;_EwOzW{4bz~ytHuT8H7 z0+tIRa%-k8d}Pu{RtDhP(fjQvx2U)TV49hBGtc#P!Ey9c?#OY8Pwm033HEdkEq)H0 zzs*I}UO+vm`|!e)3KL`9?383WDdm(I!TXtUdA)ywiu>~-3URcfAGNF0U8NL{38WL- zatgC+iQIP1jsbA~#U>g>k24b)9s1KRjw1?^*?z)-?1S?{QPR)qo>aWLzZsi0yEzn7 z7=Phdu;oV+^Bee1U*rYhG`&zwujKZg<=Cl{Lr3D#5>C@+zx!Z9t9~L|WQ{0f%%+|P zUONach*yN%+VPbCW!RZvlvn2Lw-TGpu4Hn3rXIcd^~k)unFsbr7NYG@!~3&4qP7zC zc>L*<7d&t3Y~f3}plF#S>+8Ovb5oJpJilsX&6o3o8T`P!Kag04@lcioB(9&&1Fc>Ja6-S$gGo1Z9ALC2;#UH=-L4 zCkj=y4{f@KY?MJGb+aB=pJ8qHaq4y94;G4;x}=D>n6q`0&#|w36bSc zPuD23`P=n9t4vg~l(T}K-fGL5KEg*g(eT|{nKUgr<9N(Hg;CQxidVgQh3kNB%FJ|7aLqKqCAa#4c<19Zsl&viW>tj}=9Eu!(YSyP{G++rs4GNgf2W-?ck z3%CN#*(Ud!R4KcBw*T0ER~srtP0;~R+{Z_%{b-=uI%(WWQxa1>({}!{LdBi;Mwuc8 zH!2GoJEzUe=8YDvdkR$_Z_-h2u}Sp&uo0Pg*z|Q2qg&@YM%gDNb%~9dc8E4q;uA|n zt&A%0lzC7`Jos?uagA(I8(63f!+kZORN`XDQOdxINZOolZjaI9s_De1lu{I-&pUg1 zF3|^}QgMZF@tYvD+0@?Vfql`F>eBGG;B3?Tn+87}@bVUKJJ?5)IN82$`{&)~Wwirf z@(>EKht9J*Ab|x+=LJjM$jgZNc^K+=z$sSysfGUM9v`1xjjnb6^d?a#b$kaX<&G_}1O{ z{qGAOMk%;yy0ajqpoC#6wV{ygU65L$ERdQ0&iqZPbvI>Le*)w2z)YiIsRF0?o6*(m zdZN!WLJnr%ez8BWAE;~Yi)(yNMcW$T{mSR^u@qyX&BwhNk5sy=$x{fnhNH_m*5QL^ zJEb!h^IKMAX~KjqM7&}4ML5Q6d;~6$3@zgB63IY}QcRH=6T7 z_T?Dk+c7-z`!>zzbx%ClE?ypMYDTH)&xtH;Y_>(K^-@M#E87Fi4j~}M(fe~}3DVpu zD&o#rGqj!!?72zTT=(s?)ojNr5>^LI)dP$=-&If-Unc~3m|7|p`-qnHBz?O94&U3l2l84i5E9K ziI1ag%0`z6RgJ<;!~1XqlwsxM6uf{ToLZ#8Tw8MZv+tc3RO~Jy-N8`B0X8(F;Mt@u z9)Rgt^;cEyr;!8dG=4hiBDYh^zxTm~=9Cb?WsW5$U;P?Jb?hDA1Qrcc#gmy0haHRv zu`ie&sbX#hh_Y(FEHBnbT$e;M>94WVq&5yAOttv{cwjsa;4piHRZi-fn+GI9@0gc= zNedIcny>-yW&k{h){hj4%!jEO->d{R_%o@{JuN~eWvvfL$utNSviJN7#N?in0d2uz z$|du&(-%27Mm<3n&U?O}(?CM}5AK;0mf-{7_p_04f+i2^>#Hy+G=3KaYp`ogz*YjiJPm)uns8EXL6Ph1gps2R{7V_Rz{-g4SjG6pY|SygYIzJ9G8xqku9B8=3Zi*#-cA z5ZU@#a;(DcQ`S=s*npp$fRfKoTP_Jhs0erpCpr$+zE;-1dLdV*@=QfvQ3~De6VTVE zR2DicoB`jZr*VtlXXeubzq)St5dHG4tRHsnMr^UIy|)O_qHyY4JV`HqGIZUHnzuK` z5!6`HR2Rim>QE%+)$G0E?=EUP$TVT4`jH*PBdom<4f`!2d%;Sh8`W^GGumX( zxezsDa8cV=Mki%%LcOzs;sla<@_sjc%>)!7XHWpyx_-M}`vFJc9bomK9LYBT*cl$B zzeYB`)^^Ais#Pl~Fy+6#H^Wq~J&R$vELkOyGchE{HPj*P(RWx;)Hd zQT=tJ!cY>PD%~0^@ZeCfSkWRYwK8yXA)1<53z@b4DtRDq>fo3RVEN(c--Rrx%@)0B zECZE%>oV)zr!-E&_$X0k5bo_^PWYoF`+mxXNyRnqb_2=?pd?j;mOF*ww#9=iT)VNJtcpvI+v8?4Xmnji**y!?qHK3!#?6`l`W~qPT z2X!k(7aHs{^A}g$+BpFMAIdGjX9<0PB`6+KJel{P{mblP!}jaelbrpzqZbz4jjgmJ zl>+&9zI+e-vDc9@>}X_*S!=LW-R;<4cmNGEhF=^GjGdt^J>LG5EuAq%C2K0x3Hl+f zxs^mve6>(^yrPBw;QXbncWa%)Y$uF%0N1bFP_*Bch2}$rvUzkuI%mp&$S2`^bZKeMFIRzQ)c=wkQxe& z1W5`*gb)xZ2uMm)SX5j>T3T8VBnAyG_;+)EL>gl zU$Jga3SdVJ#zR&_#MjqX*jHQ_g?11Tg+if!G{nS&ZV^IW{_YqXKOuK7&c7AZkzNS2 zlLy8L^c90@{mDwTj-2a^af!EiU)p7U2*tjE*I_e7GTLob!Cp%eDQB^4^6*Xyb87UP}Q4I)G zMFXOtDk-BW4V4g8he-a-RYxJb-H`5>zqxk*%a!@B+&`n>=5ZTY9f@}GLE34eQEs4r zq%7<7AG}D3i9uzcGAg%v(Ese^pIp2D?nUaqdJ(xbBl4%Q|EJM^U)@g6pY6ZL_V)1a z!AH8^PCNQ`teJkd-va<>ZmXwa?Du)&k-sbJtpCl}=rRN4fd#yXHzsO#s8s#?Q@fEU zS5jR!)AA)QhbOn@t8SlK>{8#p`J9gU5Rc5_M1;aoEu#2r# zl@8EH?=nz~`>$>-9a;@+ej(>Mf$R&XNoc)A$b_eUS)-=qkny^4iiBcAZS8|W>iZnf z<~jQL1oi3r4Uw{BGgC9mJUVcuk(}38JX`oy?ynykn~Yu0NRZ2#m`rXJ@X^uHT}x%} zkuCYeIb*D?mHF_AB4Xaa2w3GkLJeEW30}!lRn>&#{iyur5(#){Q>W#E#Euo4(`kst zZ(}|?3Sc%)59tKH0K3kj$uG+nYFm)Z7ycxZO zKqy}mU36v`9d#qDWPWpD(Kx^C_pUM1(-GrdVAx|_DtbIXow)7D5XkQ7=23d46@uZK1- z$M+&+(5&%&NNof5M;Hn{5--;FNm9(;zs(K0pNOv_00q>v z|Ed6$A!E)p2oF}+iKE=)CrkT%WmxO;bmGW4=DmQ!7rvl4*2tHdph?amuC52IWEJB{ z`C%~yEK+h8!DXc2lj3_h^^3MR05TpbqDXy^_Yl@&ttZ0x(WC4RJ3#Phz3JTa$EUjT zxpcvuo$by3Cu(-lK7K(R1=A*(;k!z27*ZS(Y`&id%h;-wmw$jY>qV%0=B}c_u=flKftCEj3C+m&d6zwl*py?oR(EM~F8-OijXZ=*4SI0YEi2BRXu{W&af z^L~>>3bkDKT<}nsy5+gOfB)!ZH738mwpdFv;1Dv^yWh1=b?0ZyPZ~R6=}SL!gap|$ zq2MmUCmNDK=be{gyTt161Q1dh@6TVCc=M*~eXPKJc*$u7*7r$rWt=B4x7@fm+Dtn= z^|_7&6d2$o$RBQ98kBjZodkmA<2beASNGac>nX)iYi#Xd=xm}I{VrzuMa~|5jL+Qs zmm`ojmv)T^MdC8Aa4g2~OCce1!LU=bBIQdl_9<;((Ii2))8G_jNr@>-PRJotFsXD} z4gzx;FRc?sbAV`pp;gVy34@nIrZq8|oPK)@N`uXY@u0v%1lcQa^-J1aVVZ+XVOy={ zXlyD)b^(G{2TMu3_?>E%ld25|{yc*;Vc*c!sF$m8>C3>*O%Oiik18&;?WgH_Du)*Z zDIbkFZC(OdjNjEeZsmA2;~x?eU`fPPK*_Uf?K4*IY`mk;J;iV!x<0A~`;UR+zCS*<+SH!p#H!5Z2U-C+2^Ya?~)U7a2X zFB~HZ=(8nj@;m4uv={E&!CE$Pk)A`G1~Z>V%HdJ>96QE7UXQY2V~@fX-@&1I+51)f znzH&;MxuHuVWnqU^=w)z$ccmpOkx#qFYM(Qajlf2@nHyzM^(a)MHOMg8PC|^QDpro zl~6MYlhuy5NinsoqAXbKy#M0|J$-*DD)?x>A>_%$*JU%o8U2SoV*_K4JT{hs7+=s) z!<65RikER3)EDr|x}QAT(!r&m4$z&B>n<^FE!=UdW)!}hB$8^a^?psWaE9r&G9h15 zTLy{6VGKp6i+eZ7cReJ4Nz55NJ4(M?Fv~Za5aw#fl-B#VbsF2g0dWRT_c|?l zDslKUSvPiQ<1h#JM^(}XSY`2YC0GiIl^pSwSb--b!mwHC&W8kF-w2_w^yEqHFpvqs zD%b42i=eqx?bXA1zC)nTZqNxsbjhOQ=SG^>3;W@@i(K4zyre)f6$Psrjuadq*4ENy z_^umfj1$sOO=G_XfNcpRBt$J!5`>4)kj?a(nkEpwb#Z)6`zGeQQGUtC)y6evcy~CR zlxx)oPfz%u`|y2Xts!A506OP!Zb4DSbV7MC1$m`Q;dC;BV}%!H{4xashd4IH&XrjC z@$~q;%`Mtk18QkB^2mU?Uvgn!KTE&s!&v9J+zsZPbdF4L zg`R<$2&auDSD_33@%OaQPUdpAr&-!{Ybs({1J*S$%g=EGB8_C^gx9abqY~pa(0p|n zmY(T#HompGXQaT`M0_1H3BZc~7hruOcL z8lcvuaIO+)*pez3+O9D@+1Ute(ED*d9{N?YK7O2Hsg1f24*S_OF= z;V+_DpxuQC+<2eLCd~mjPuvMj=w4qn)-Yk?VazBJgON*XUiqDrrI`3&J4fN_nGh&x~A{0_N(zdB{aVBW58GGi?4GQ8B^>(-PlpV z!tKB+suW3Su0xDM*vGl7cm(4#EIEuU_t(yyiezeE)uv-|;iRq4dmhE`f%{&n?~AU+ zUmndceS735`Qq~l6RFH|Jmu-slzRrOu8xj=F>=*rop?8`A?$5jv-8h=s!;W62Twft z(v^cE*!i&t-<^ys?VfEt4&Aq%$r#(|7g_J&K5D*{Dkq(W6(6*Ra@G7CX%Y9v*XxyD z!ZfAq*4=P8_3l!ZKooUUKQ#%Agnqd2b&{B1B(s)V9W9;n5^8hK42m2@S$2v7Ixa`W ze~~JkcXOkoOy9E+NTWW-t`jb+@mH^&z34gbg|gAm4pi4PcCT?Je1KtJ72e^5t@$RK zs-Gc=T}%@=y!j}&S+VVrhd3=#qL5fDsm)MKNs?{qbLmzBIZku?QgpJep+{3|?fn1wAp!FkqL5x*iJgzo6 zT4@KsT$z)>$XeDnNZ!gxUN(!Ly^;`WfVt<>$0>^NsmW`L=IW=R)(C5OCzZ&r$I1K* zA;UF!0W5j19@5#H(azWqb&sz1nXge~v(xOXS6FuaU#@G@GAQAu<#H0cq*!t?vRyZ` zj;pnS8K1XO)yj^6j4KyoRDr3$>pdQ&>S6cA*)Q04!Q8H2=@Om8XvNBEQ6ZETm6~^& z#fYG<;kf&ePo~8r0-eYo3$vsics}0wp@QL$BPQVo;u_LR=o#`w^Jw(>UwlxrhQ)pi z`TZ0JKsq2`kUR<9?5Xr*uk3;|sBhtae`Qn_?>g5csxvb^{juQ=s_lZ{vHI+GDjCWW zA9FZ{Jtg4HiiX|reuUq^6|OF3WT|GsmWL9>+e*FllbT5hdb)t zF_#L#^4IL)R3QzNE7uPh;CcMUzKf*UWn{}3C1?2H9S+IRCA_~%7y@KSxY@Y(d5ao$ zINmOG&{ekn$jqf2trb9)$86)Lw{U4XQ{`o3$UHDHX(*|8!TUnnb4WVyC9|Q;BA>J- zH`?fmy?6g|ac){w=aUCnf~)81VG~{yWz_Aw+wy1j`N1_Vf0<~8a><4#^8NCfu2QyW zB{4OQNy*p!Ar|Z2 z$T*qcE1H-79H~|k%p+Blx>G(zAk0PlG52gdqd4L6(6S`iZ)fK;5luEWp2n~BS#Yn~oqJk3{^ZEg#9m0` z-S5E#yHT-}Z~dp|n+s{wg6=T07?!t;Id+DIlv)u_K7aoFp|*nf^PFUn0u^^uuJ%u| zEl1Bgq!@PoENPpO+_M7zYo4hY=?#35HdhCdQH(tnI#TZ*O-t(ay0WidIh30@r=h$B z2uKw#Gt6IX@pg$(g>QcL&XsA{7s3O?0sorUEpXlKdHrZ#(`#@*-XeS8`N_2AnxIjZ z_x$5kv#nXy2<41sjC+{%01@{B)9|T+ZsY3XprGrPR^PqSKEkc7UDa7Oxz&SY#9^v zMQNs|r$y`VB1K0?bz`s$vJo5mr^fux5m8)SQCjgE4iQ+0aP@WHnMT z$$Zy=aa=B#&<#Lv(VC(LJ&|ec=ikrdsLTIS?(6=a# zhp$I&T3mNi3oXJrpH+L?4agjGV3%xiMgqG{14KO3xEpS25_zy0IMpJZ*s1Qp`o^`l zp34@yTu=FO%+0(X%Y`_F7MGQhJt?9H@?fK~rzakp&?fioZrE&Z)5#v83FP1}K%V3g zqJ(&OXXd|p2}WyIfGwu_fo`QguLRFr@JIEiy$~}~pKxIn`^M(V6NsRnfqO=XZ|JwK zzm3-0E-Uky0hz0{Eg3yW^~<~Uqw%)RE|$D}nGC!pHoDokS*IKbmX5UI#xypImr6JD zf@)RQW}Ln|rr)Ja@O4d$>nJtwYXQiL&^6q#1aVa9EiH-J=P`RYcb@gho6q3dY$I8h3@KrEV%X1`&=rOzAA7s*z!wJhRzG&8296)NV;ysaXZdBZFc6M|20yOY?3 z<>CNawTtbG_q3wNt`;fmt!0L=DDNjFD59Qm+#P?#SE>BoH8cX}mp%qaaGw#t$yG}AgeP>A%PS|VmPhH# zkHH;C*Gp9Kv|P`m)f@mxnPcPM49+F39m{-2G$CGd5J`bkkQRdz3l8yM{3bHwK|@;k z)I^Ac&OANpf+l}A3fT+G;9wmsWn;+r?) zF3+@k(%3s*2FM6l$Xnbg UDHUG-^AlP}!$7@S)jI5d032$d+yDRo literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_txt.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_txt.png new file mode 100644 index 0000000000000000000000000000000000000000..e6335a13f7971fd50f645f5b121aa16aba2c3c8a GIT binary patch literal 4821 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 DwoYK` literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_wav.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_wav.png new file mode 100644 index 0000000000000000000000000000000000000000..471bd7515c93f34a560b2534ae74b13391122443 GIT binary patch literal 6854 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_wma.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_wma.png new file mode 100644 index 0000000000000000000000000000000000000000..471bd7515c93f34a560b2534ae74b13391122443 GIT binary patch literal 6854 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 Jmp80J{s-W&=9d5f literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_xbm.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_xbm.png new file mode 100644 index 0000000000000000000000000000000000000000..69e93bf54359a750f57cfc77bfc300e114725e68 GIT binary patch literal 5873 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 UDHUG-^AlP}!$7@S)jI5d032$d+yDRo literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_xls.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_xls.png new file mode 100644 index 0000000000000000000000000000000000000000..3100b10e90735c1d72548f274e29566839f95bd4 GIT binary patch literal 3693 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 literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_xlsx.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_xlsx.png new file mode 100644 index 0000000000000000000000000000000000000000..3100b10e90735c1d72548f274e29566839f95bd4 GIT binary patch literal 3693 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 literal 0 HcmV?d00001 diff --git a/config/alfresco/thumbnail/thumbnail_placeholder_doclib_xml.png b/config/alfresco/thumbnail/thumbnail_placeholder_doclib_xml.png new file mode 100644 index 0000000000000000000000000000000000000000..af6f534b95cc9d6096569d2ed6480d57d941ebdd GIT binary patch literal 5074 zcmaJ_c{r5q+nzClu}}8h7)w%^vG2xiFpM=4W-yk*3^R5Lq3pXz2}$;?jhD*46bfZu zQ&jfXQdvK}zqj}I{qeoua~#ie-`9Pd=XqWCb)A1b@mMoMCVF0a006**Hqx^^T~WVY z2+irY!pm>=bm5`s+fl5D?vx7=S-XZ_byabLYiYvNgH=xjdqL1SOyOJ3qrZ70sl`? zb{H&7m*`J`DM%xvToH;0m?BadDJQR_q;vr$i$Ee|5VA5zSt+EFs+_DU0s;H`fuCmc zcXL;@)HC=y*J-2y_oPrrsxmS`K|#_%a?(V94;iG2ipnnySy`!5gcLc%mx2$L@+FJ> zR?s7mUH!dC6fdGL?3W_mg&085fS*?SpDXx~{*m=1|J|n3hRFouNis-j#IGy;24XP( zKh($PA2gX_N%(KQ|EDn7I)p@!u_Ta*0sgM1gL4=8m5QXQ>rcQ_i2l|@qWAAEVm*lz zBH5Ejg6Uc*z%Y1MFW+DBUw8~g741u=;C)>QXgv-1se-hZmz%1htgMQ%in6?%vZ4wS zsgF?6)<@{;C@32!smLSs5DLG!dPLU%AA&FCH`ndIT!nw-{<4A(=`^z*!QU&8;AY@Y z^nv|#WmT_#uSND>_5S9%{d+BP|H_p)Ek@?oVE@;ke;=K?=hyZh+nxsh7@y#K>URHA zTc--8@&W)*1GJvDb@1f0Lx?y32c8r64abeimGS04?^*%>3OY131d_%X17n6f)RFM{ zNn0LJSeQjaFHfB%ZkvsTf~~W4yoQY*R&L6?{SCs7-&8kHuIKh9{ zbW%m2Jbt+w8F%1mp=S((VheRJpKO+!E(7*jz6&zRF50Qdu6DAiNspVtzAz454!%Hx zI!8*gKE#+lO8ZFZRCrxoUHy#$z~!~8yO&6cYMCjIY3|1L+Hl`~>t}p|N-T9~DQ|;m+9&Z^{xL1loK2xFG z;85ioL#FKTK14GO75DvQ!S^;%d@2t_xgSOfVa9vfT3?Fi4?GiGJ_HUba6PIJgQf^+ zNk>?*wx&=7F|kGDIo>Zk`|Pao;KHrpwceE3S8WavCvm~*gA~WCI7<|CC|K7SWIXMz z{%r9*muCh}C2%!&2V(?~vmJ7S2+m#S%9Xu|3zvr!VQ=kjkK)iBgU3A!GqujKBxNv< zyf<4pSZh+=3z6gpgREdBCoNdr{qjjWUo5`1P@$6UrU-2ver^>=!w5v%dQiC(;cs=h z6c#qh(Y3nXlcL7#v9B^|Fle0BGJv)-!XBhw08@oT!BI#kRxC#Z5@j5T(KA zFZ!fjT`5fuX1V+NQY&FlNBo{1$*7;K82MzjBo3CVplgk6yS|v(HZ(#DE%Bo%hUf^R z>n5u9Lx5@g(zjQ%n`a7W+tWo1OT1|3%l54uORx33@pbT??R&yunLsbNqHLBCb+?+$ zMfkI8UEVNiQPhf26XP3g)p68sn}(O41f^TpSZwdpH(Ghv*w~znu45HaVDX~i>8^db zZ+u4)aP52Q=>8;%^t|hWbFeDmrs#SVDCl~M2jleUs=fC6!#jvz9|;~ve|Z*Df2rRO z_%p%i2S;h9;yjQp7n;Xicb6>_J2}jrPaQ!kI+*Sxif-U!xMI-2x~-q62v@!quM8Yo z)K$OK|83)YHAcZi`iUnpZ%?c$+m92kcITSHDiJyLc+wmLt?mlW+ndp{JZoLCYepPb zW$Um|m@v3~zz=Cx%>v6NO{6>waP0lTCjHgu*?Qz$1(@jl=6ELqvKN_K=O7$L@JA?K% z-{MEBLd$AuM!5#LO=dvlJKXQ>XKA}QZjhGxkIn=$?6K(K@vGV`wKSwH&mZ3XCv3OW z3ve_xR@p7ePKl@x@%XV`M2*Pi@YfyY)z{eE;gOL{$2we;suUr=o@J~3 zwqlHIt(*->c{P#|ur3M$MmRb}+a`b&TwwF!7r4l@U~NghNF^Ngl$4ajxoYgii#@rVE>4*2H-WNy-M6KIU=3laoSW|K4 z2jmASy%of&6I0wS&q^L~(uMjhc(G6YzYMuM0H8y||(FIBnL=iIvS@g~5wA z-PYFEbHwGN-%?N~hlb1Rb>j~mhzvXJhZPo_^G_x+Nj^u=+@pAg5`vV1?ZlLpYd7OF ztLXp^DWN>Fny=0G4)Z%<^DCl%=u)99^GNrMn{Wr^24y(~v3w7J?k2st%*@QP9mbOB zI=h9jy;Zk#y4Vk;*exW~-+IQfH;C-;X;X?t1LwErdV1~}7+(qK&Tv9$w5;#Un4Dmj z$NefkFIAbx@^F8(SV?&w^;UDq?wZ4=b@N&NaB+_o{y)?tZT*T~w*6rKVy;2O)5Toj z4N6rR&?8EGj#)lfj?UU_%UG?()hHqXje5zcsdDa}4S6Mg+7YHUq$-BIC41522iKVR zji(%qa#|2i!2nrH?>VC3`pe^V-W9{8(tJ)^mMEL%Aee>WxpeX%OY_)H!9Kt}Kj&jK zdEK1i;PU6r-onAcm(BBK2by&Y@|?CC)l7{TFwbYD2pa&%VPczzjb_{W-9&8wVkr%@(P||4AUN1d4Qh5EzXj8 zX}Dq_u(50pnL8J5F9-==sJDj9sy5K2q_#wNzm2zbcIPSP(bp0-A;B%EKh(z})S~*8 z6m=XN_TE7}_$vEA;_l{xbJIiLq&bz&U zp|Jk+ovU75Ngpez1&ku-&hld)Ppn=LlH#8 z%AZOp`$Bz_MaWQ<{{(mj`dC zke6WF$w?svhTJoEig`4V(h^|3B1yJ0A}$}_(Z|Hx1|Sp#%SSZqDh)c% z{Ir{vy>9TM8_L@Nq_|>IMC~55PYKvWi6`;F6TssPx5ttS^S5Hd)hHaF z6GD0WhH1*SR=^?9W=&f8%>Ag*OCJ`K7RG7Ic+WLH^JUCVm#IRX4G#f^|(n z#k5!o-Q#>Y#1xyJkiSBV;YNGXP)~!wYrg4q$;(v_KQ*fkLHvg}d%i6a# z1fH{eqJD1-EF^@|!wW>ypD}AO)pTAW1<&*&hCpj&gG?zH1m`Q7r2#VH#0+jemW&>; zk4>RN&7;^s!o)EJ8R3hTR52tgu{RjbN!Cza?;P;R&}D$XLSvKqj zUJgl0$}GMZ->LHBW7E{9xKvPQg${)I&87q!`!w(FcG~T0+bb=e6_%@oXn^%8Ti_GR z=UsJIxXa^--7!!83-w#%`nfXZa3|M5^wIRVJyU$9s1X?0K2u1M_O0Uvcw|=u&p@p% z?KN0ua<8|GA6|N(hm{jslrYk*S9w%L#ip>Ba(j2eI5z1)2@o*ykjh2G@lzj^%VZe8 z@AyS?_&^7)$pK9=tS7w&q?NWoIGfE-n{^*N$9KT`2aag81WkKLCSVd%-1jX^>_gFb z50-*1eLgCthnw_9J=D%k@GDtJ2rm$w4+mN>L;e68&H2&gszO)~Mp05{81B5z1`O3j zWWsLwd|!9Wy+T{~n7n8|hNfb-!S7rb2F8jORaTzgD5LevwA&7PO_Np091~g48nrV3 zLOp#kILK5N{*&JB>6PqR^J2buJzPgzGHEX2d;9Rii>(|NF9WBZ`E-QsM>%x{QYdvq zx`J?j6Q{ZM*u(_Q)Ve&+fn`Ukz_rOP38*JNiB;>Uk%prY1fTU%j}odq%>* zq7=z5zvblW{1OAujm~0-QHF4U<}behdc^Q@nA=wGZ`r$+y<+vvXlnI$PR5i_EyICN zw)vEN-{+N(YiLH!oZn=sgaGVSDo5P%#5%+kU2^t~J_W5$51?QoMte9_-tx%1*@;Ww zkt?8@7XNcqc0+^QB?dH!C$kL%hLO)<8%GLiXTNhX8%nZ7W+YObgR5^iMNV_CA)2JG z+-*Bs5NxRw>c-N@1RFD2?3+wX9xQj3&vFqGa+VOs6(M-q;jGwEEsW#!qX2fLcaJ={ zHfE3R3v5aei?CnDo$rU4ouED*(+c($T$L{T#=O@&WbHFhu@}41q#r$bJ*M}T=YDgm zMh)A~iHrk7!9Rgx{aYXM_#{%9?Tvc%5EdJ6gga zetdqO{nJx^^6Alsi_c*I5hE|{h)2HQaw&&Ej%>1{(T>mqxvc`#{bg#a8jh1(wB)sc zA6hrr9M3B{9Y~3ErNwO>ve)>5&(-4>4Vo-Z)J%^>qx3RwK0Oib3bF2kN&Vbd5mR0v z&C83<`eyms#J1)EmQ5_`x|v6GMy=rB(D!B}R-n2@9~Dsrq@vz()nUDnE) z(uuN-h-_s~<%krHCEjsk%Hf`ha+^f3*CX@Ll48zh=2)!;!T8D2H|{(7$S~vG596%6aa8S zNscab7n?IkJS9jSw~tW|52CW!0C37EoQlH-5a|$aqA!VT09|fwgF;9I1E{l(4cvx$ zn&?Neh@cT2B5WP;5dnAv0cvCjITem%3j`7AI7oOFKFKwBVX>4Y-zurWQ<752>w%gu@~K zTu`<*8o>vNF*WFeQfIX(TG0L?J`=6>;8_3v>f0JJSE85Jde~mK^d=nb-x>2**)1G}YnzDg6N2 z*!=&{prC)zA#@D!zw!Q`!Xb_qsYDG7F@$n~hG%b_kNmzXD)Kaqh@(?zjucAZk1E>v zQRtKqKMECc+Cc|mgTs@^`}4o?Ha190atIwq#uF_~4WMiVbrOky)YZ~L=p*#?we@uo znwn@h!UPRRqjdDm^bjXCP2oB}xTX~Rg&-oC{)0>SFBkDs?tUu-QQ4kNi8N9ukzhun z1VR2z8AsaKK9z(GDO^2wC@vnt>_w>|7MEc5iTi?7)di@Mxy+mCfbsdQ$kOk zn&X*}glC*v985%_kU;U5=hQDfI8=LxR6>gQh%k<)l27<%im><4JDadKa~n-wrA>}Q zp0Ee6b9j$9t@&Ttbb6n)xmtSI$w}GMK^#5VHahkRnQnAXPpF(cYmBC|Ki8x-+{033$}cDWmI%)vo<)h^y$;5A85=eK8B3ZUXPr6 z@9f)|)zwv&ii)a?YYirEFaw*qJ<@eKxl>*|>e~mAA-SEn;rpIhtDduWpUtiDK0d_i z+es6Obv-U;6a_wX4b=@|cHb7_0OYJKNv@;8$>W~>3vbL!I(GKCgh%UlnBxy{4Flb*-%#7ap`HnZIrvT|(4<5kaiNyFyIQs^RUqHm<$!xwe`;7dffbt&Z(c27$=yL-zAU=$xHlNSB-a(JsMMc+{ z(ZGYZ4dgx9(2SOrQ^y2Q_uedi&s^T)oIKxMJpGGTsG_2?1mq#XaSWO6N`9qF8ASi+e8*XZlR=(BBiU6~8h&tk3A)r{jDeWGV{yJX(tx7U~E zzHUlgH4FfOWyIpGg-?E|8_&m;`(dq1Qu9`(o^mSs0#f+KQAJFu{MhHW&81&IQ^W;h zC0iC=_80UWJqM|_2IGXw>JFef00reBPLv95qUl2U3y!U(=9}-nMD83pqzzz5+QNZH zcCqlkblmMk`SS2e-GatyvV+gNxG~Y#4C!TW)dmvuSyFDLqmyTbxJeJ8>vHtY&W;(+ zbjV|(YIlV2?Dy@BjQIx)7Z1tVi6{QK@!)&j;}3)G&dhj!TKX)$(HgZD|C@l&tBypO zw2BS^#_?z-lUett;FSTC!krjO4MeSr8yt;=n9phgSfAqB0nFA?-_hW+Z(5u4l;#J= zbOI{VR5)My&-e=WRVy;2i1d~9wKZi`9!5rOXbV=l1;Ckm9&L@>%)8DjHz>^C5WOH_ znRnZW_p)f*#l;nEKM)r_u1EZ80s=BbA5}&`Umrev_?7hTOdGxa)yM~>plNWlLtrqheLZiWuL&!Fvo?e#NzIk3eL9#pcRt=iL6ZY(ljcYtj* zpreF@{~}&lrIB~zQzRKt9>8#;k(=P^Y$UoPEuWV-Aef0HJp62 z&&6f}@Vddjv@lIHCy-Z2wlFqX*4_kn8@@?Z(4yE!HtMldlTI8z&SfMunfmFnk2RKQ z@QiiK=hhQ}=HlWLWlp)7SPqG~EJ3b1oye_^zVzFrmXWP5OD!XG*As?G&qcCk?(m&W zJ5MvDE)K)oo&oIg+_6&di5qFZfW4vl&P?a&YiZ^s5wjW=S$GOOtpCTGy0^@src z89CYKv1B~5`?Pez@6U!Sn zs$Yn4wazG@Ie7=yA-|p|B|P%5f1<1bHjK=Ey#2W{X3z(A1)YQW? zzo zdZzChzkV&3p=k-H2$Wl0O(l=DVdGk`3x*xGHBb5=rk+wcT95|MR_3FYUtZhRhxWu@ z1gUhm7&@rwn;L~$BsJv{E+PL^sL(%Ve&EwC=eBaavchU~G5#z%)S_sF%*%cqiDSE! zQUV04O`s+vDj#nj+`}HQ_2>5y?wRAe0gL4(lpm89h+TNmk}caKG$6p@#@Li6KHzYt zc`QlTd9-+$f-q@y4ed-+stB@DYcy4!I1Z{cVRk%>dK*zAm`&g~HO6Fux_oP_%fM-O zbz|u5?g$Qmb8T}cy`l|T^r)i=Tb6^jqMdY!$w@o|A6K86ZCGB5xb}NahUuO+f0ZcL zE%|=`3(;7(l9b#j>>SX;O;C7I|K3kM?;w!wWW@vu_)-~d>oL1g)7vs#-OmRQfJ@-? z5Fc|;vpLU=kscTfbh)>U*TrsaZTVrb4AgDmO82%tJ9)bSH|F;;N9Z6O)5ZGK#2CCj zdfwEb{(Zi5K7Vm>qmgKhml(BLLi23Ojm=-vk~75wjn{O`4C?OOxf!}N23x;7A(u?_ ziq>sr)HCuNuY%DW>la)YtcAnYcqLNv1Ms^%UNu23bOo^_{a%FeLl{u#>}79qbm?e% zr)l=WmpB#sd+Qq>OeX6UHfIy)p0|??;mSK5 zvnr|KRgjbvx-}hJe#Dh|^J#X7HwOo7R#jQKruF?_U&Orm`Cl}wlt~q>)E{)fWrsOC z7tc4AmHB6?)ELIihk(0z0!G^Q7$eJ{KhJFb!PRV@-=JGMQrMS#(W#HyHXmsTgTXQv z3VK>EdA4&)%dq(Q8|WWvqas)5oI!Ex>!IIezg^y(f5}Z=B&esPotm0xG;5;Gm#HuG z<-cL+oZPX0Bw7v&=YSkpK0Wy$0A|T;-WZrk?m^T5+2I-Iwyt}humwwQnof51)@Q9r_>$FAsAqZ%qF!{P;cI_O<`T>xwf99}S^6*4t;^?Ip>k-wz=!3j> zYk}(0k5JFn2y|X&&8BpXooM&xN#2qOhNo!^N-IV?*5p#Hs@sZF;sU1eK!79MxII=^|(I%`+U(C}UB0AP`2A*p`i#EItJFSE&#QjCRaP|UWQ`>%s?XXTJ| zVIz=0?Udx*!xPR4avBYyy}Rau@!t_&S^fk0b(|mYz<6XEOy0EmXR;4zU`tCJ4H+=+e^hQ?|M4r + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname}/${spaces.transfer_groups.childname} @@ -49,6 +52,9 @@ init-method="init">
+ + + @@ -81,6 +87,9 @@ + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname} @@ -128,6 +137,7 @@ false + + class="org.alfresco.repo.transaction.RetryingTransactionInterceptor"> + @@ -250,6 +261,7 @@ false + diff --git a/config/alfresco/usage-services-context.xml b/config/alfresco/usage-services-context.xml index b4baac32e0..95ec47f51a 100644 --- a/config/alfresco/usage-services-context.xml +++ b/config/alfresco/usage-services-context.xml @@ -42,4 +42,20 @@ + + + + + + + + + + + + + + + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 762406105c..e8c8ea905d 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -5,9 +5,9 @@ # Version label version.major=3 -version.minor=4 -version.revision=1 -version.label=f +version.minor=5 +version.revision=0 +version.label=a # Edition label @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=4116 +version.schema=4303 diff --git a/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties b/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties new file mode 100755 index 0000000000..96a13ea0a2 --- /dev/null +++ b/config/alfresco/workflow/invitation-moderated-workflow-messages_ja.properties @@ -0,0 +1,34 @@ +# Display labels for out-of-the-box Site-oriented Workflows + +# +# Moderated Invitation +# + +imwf_invitation-moderated.workflow.title=\u62db\u5f85 - \u30e2\u30c7\u30ec\u30fc\u30c8\u6e08 +imwf_invitation-moderated.workflow.description=Web\u30fb\u30b5\u30a4\u30c8\u306a\u3069\u306e\u30ea\u30bd\u30fc\u30b9\u306b\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85 + +imwf_invitation-moderated-workflow-model.type.imwf_moderatedInvitationReviewTask.title=\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u30b5\u30a4\u30c8\u62db\u5f85 +imwf_invitation-moderated-workflow-model.type.imwf_moderatedInvitationReviewTask.description=\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85\u306e\u958b\u59cb + +imwf_invitation-moderated-workflow-model.property.imwf_inviteeUserName.title=\u88ab\u62db\u5f85\u8005\u306e\u30e6\u30fc\u30b6\u540d +imwf_invitation-moderated-workflow-model.property.imwf_inviteeRole.title=\u88ab\u62db\u5f85\u8005\u306e\u5f79\u5272 +imwf_invitation-moderated-workflow-model.property.imwf_inviteeComments.title=\u88ab\u62db\u5f85\u8005\u306e\u30b3\u30e1\u30f3\u30c8 +imwf_invitation-moderated-workflow-model.property.imwf_resourceName.title=\u30ea\u30bd\u30fc\u30b9\u540d +imwf_invitation-moderated-workflow-model.property.imwf_resourceType.title=\u30ea\u30bd\u30fc\u30b9\u30bf\u30a4\u30d7 + +imwf_invitation-moderated.node.start.title=\u958b\u59cb +imwf_invitation-moderated.node.start.description=\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85\u306e\u958b\u59cb +imwf_invitation-moderated.task.imwf_moderatedInvitationSubmitTask.title=\u958b\u59cb +imwf_invitation-moderated.task.imwf_moderatedInvitationSubmitTask.description=\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85\u306e\u958b\u59cb +imwf_invitation-moderated.node.review.title=\u62db\u5f85\u3092\u30ec\u30d3\u30e5\u30fc +imwf_invitation-moderated.node.review.description=\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85\u3092\u30ec\u30d3\u30e5\u30fc +imwf_invitation-moderated.task.imwf_moderatedInvitationReviewTask.title=\u62db\u5f85\u3092\u30ec\u30d3\u30e5\u30fc +imwf_invitation-moderated.task.imwf_moderatedInvitationReviewTask.description=\u30e2\u30c7\u30ec\u30fc\u30c8\u3055\u308c\u305f\u62db\u5f85\u3092\u30ec\u30d3\u30e5\u30fc +imwf_invitation-moderated.node.review.transition.reject.title=\u5374\u4e0b +imwf_invitation-moderated.node.review.transition.reject.description=\u5374\u4e0b +imwf_invitation-moderated.node.review.transition.approve.title=\u627f\u8a8d +imwf_invitation-moderated.node.review.transition.approve.description=\u627f\u8a8d +imwf_invitation-moderated.node.review.transition.cancel.title=\u30ad\u30e3\u30f3\u30bb\u30eb +imwf_invitation-moderated.node.review.transition.cancel.description=\u30ad\u30e3\u30f3\u30bb\u30eb +imwf_invitation-moderated.node.end.title=\u7d42\u4e86 +imwf_invitation-moderated.node.end.description=\u7d42\u4e86 diff --git a/config/alfresco/workflow/invitation-nominated-workflow-messages_de.properties b/config/alfresco/workflow/invitation-nominated-workflow-messages_de.properties index ee2be7fb4a..72c23f7590 100755 --- a/config/alfresco/workflow/invitation-nominated-workflow-messages_de.properties +++ b/config/alfresco/workflow/invitation-nominated-workflow-messages_de.properties @@ -4,18 +4,18 @@ # Invitation Nominated Workflow # -inwf_invitation-nominated.workflow.title=Website Einladung - Nominiert +inwf_invitation-nominated.workflow.title=Site Einladung - Nominiert inwf_invitation-nominated.workflow.description=Einladung zu einer Share Site, Nominierung durch einen Site Manager # Invite Task Definitions inwf_invite-workflow-model.type.inwf_inviteToSiteTask.title=Start inwf_invite-workflow-model.type.inwf_inviteToSiteTask.description=Eine nominierte Einladung starten -inwf_invite-workflow-model.type.inwf_invitePendingTask.title=Einladung, einer Website beizutreten -inwf_invite-workflow-model.type.inwf_invitePendingTask.description=Einladung, einer Website beizutreten -inwf_invite-workflow-model.type.inwf_rejectInviteTask.title=Website Einladung abgelehnt -inwf_invite-workflow-model.type.inwf_rejectInviteTask.description=Der zum Beitritt der Website eingeladene Benutzer hat die Einladung abgelehnt -inwf_invite-workflow-model.type.inwf_acceptInviteTask.title=Annahme der Website Einladung -inwf_invite-workflow-model.type.inwf_acceptInviteTask.description=Der zum Beitritt der Website eingeladene Benutzer hat die Einladung angenommen +inwf_invite-workflow-model.type.inwf_invitePendingTask.title=Einladung, einer Site beizutreten +inwf_invite-workflow-model.type.inwf_invitePendingTask.description=Einladung, einer Site beizutreten +inwf_invite-workflow-model.type.inwf_rejectInviteTask.title=Site Einladung abgelehnt +inwf_invite-workflow-model.type.inwf_rejectInviteTask.description=Der zum Beitritt der Site eingeladene Benutzer hat die Einladung abgelehnt +inwf_invite-workflow-model.type.inwf_acceptInviteTask.title=Annahme der Site Einladung +inwf_invite-workflow-model.type.inwf_acceptInviteTask.description=Der zum Beitritt der Site eingeladene Benutzer hat die Einladung angenommen inwf_invite-workflow-model.property.inwf_resourceType.title=Art der Ressource inwf_invite-workflow-model.property.inwf_resourceName.title=Name der Ressource inwf_invite-workflow-model.property.inwf_resourceTitle.title=Titel der Ressource diff --git a/config/alfresco/workflow/invitation-nominated-workflow-messages_ja.properties b/config/alfresco/workflow/invitation-nominated-workflow-messages_ja.properties new file mode 100755 index 0000000000..d00339c76c --- /dev/null +++ b/config/alfresco/workflow/invitation-nominated-workflow-messages_ja.properties @@ -0,0 +1,56 @@ +# Display labels for out-of-the-box Site-oriented Workflows + +# +# Invitation Nominated Workflow +# + +inwf_invitation-nominated.workflow.title=\u30b5\u30a4\u30c8\u62db\u5f85 - \u6307\u540d\u3055\u308c\u305f +inwf_invitation-nominated.workflow.description=\u30b5\u30a4\u30c8\u30fb\u30de\u30cd\u30fc\u30b8\u30e3\u306b\u3088\u308a\u6307\u540d\u3055\u308c\u305f\u3001\u5171\u6709\u30b5\u30a4\u30c8\u3078\u306e\u62db\u5f85 + +# Invite Task Definitions +inwf_invite-workflow-model.type.inwf_inviteToSiteTask.title=\u958b\u59cb +inwf_invite-workflow-model.type.inwf_inviteToSiteTask.description=\u6307\u540d\u3055\u308c\u305f\u62db\u5f85\u306e\u958b\u59cb +inwf_invite-workflow-model.type.inwf_invitePendingTask.title=\u53c2\u52a0\u30b5\u30a4\u30c8\u3078\u306e\u62db\u5f85 +inwf_invite-workflow-model.type.inwf_invitePendingTask.description=\u53c2\u52a0\u30b5\u30a4\u30c8\u3078\u306e\u62db\u5f85 +inwf_invite-workflow-model.type.inwf_rejectInviteTask.title=\u62d2\u5426\u3055\u308c\u305f\u30b5\u30a4\u30c8\u62db\u5f85 +inwf_invite-workflow-model.type.inwf_rejectInviteTask.description=\u30b5\u30a4\u30c8\u3078\u306e\u53c2\u52a0\u3092\u62db\u5f85\u3055\u308c\u305f\u30e6\u30fc\u30b6\u306f\u62db\u5f85\u3092\u5374\u4e0b\u3057\u307e\u3057\u305f +inwf_invite-workflow-model.type.inwf_acceptInviteTask.title=\u30b5\u30a4\u30c8\u62db\u5f85\u306f\u627f\u8afe\u3055\u308c\u307e\u3057\u305f +inwf_invite-workflow-model.type.inwf_acceptInviteTask.description=\u30b5\u30a4\u30c8\u3078\u306e\u53c2\u52a0\u3092\u62db\u5f85\u3055\u308c\u305f\u30e6\u30fc\u30b6\u306f\u62db\u5f85\u3092\u627f\u8afe\u3057\u307e\u3057\u305f +inwf_invite-workflow-model.property.inwf_resourceType.title=\u30ea\u30bd\u30fc\u30b9\u30bf\u30a4\u30d7 +inwf_invite-workflow-model.property.inwf_resourceName.title=\u30ea\u30bd\u30fc\u30b9\u540d +inwf_invite-workflow-model.property.inwf_resourceTitle.title=\u30ea\u30bd\u30fc\u30b9\u30bf\u30a4\u30c8\u30eb +inwf_invite-workflow-model.property.inwf_resourceDescription.title=\u30ea\u30bd\u30fc\u30b9\u8aac\u660e +inwf_invite-workflow-model.property.inwf_inviterUserName.title=\u88ab\u62db\u5f85\u8005 +inwf_invite-workflow-model.property.inwf_inviteeUserName.title=\u88ab\u62db\u5f85\u8005\u306e\u30e6\u30fc\u30b6\u540d +inwf_invite-workflow-model.property.inwf_inviteeEmail.title=\u88ab\u62db\u5f85\u8005\u306eE\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9 +inwf_invite-workflow-model.property.inwf_inviteeFirstName.title=\u88ab\u62db\u5f85\u8005\u306e\u540d +inwf_invite-workflow-model.property.inwf_inviteeLastName.title=\u88ab\u62db\u5f85\u8005\u306e\u59d3 +inwf_invite-workflow-model.property.inwf_inviteeRole.title=\u88ab\u62db\u5f85\u8005\u306e\u5f79\u5272 + +# Invite Process Definitions +inwf_invitation-nominated.node.start.title=\u958b\u59cb +inwf_invitation-nominated.node.start.description=\u62db\u5f85\u72b6\u306e\u9001\u4fe1 +inwf_invitation-nominated.node.start.transition.sendInvite.title=\u62db\u5f85\u306e\u9001\u4fe1 +inwf_invitation-nominated.node.start.transition.sendInvite.description=\u62db\u5f85\u306e\u9001\u4fe1 + +inwf_invitation-nominated.node.invitePending.title=\u62db\u5f85\u3092\u4fdd\u7559 +inwf_invitation-nominated.node.invitePending.description=\u62db\u5f85\u3092\u4fdd\u7559 +inwf_invitation-nominated.node.invitePending.transition.reject.title=\u5374\u4e0b +inwf_invitation-nominated.node.invitePending.transition.reject.description=\u5374\u4e0b +inwf_invitation-nominated.node.invitePending.transition.accept.title=\u627f\u8afe +inwf_invitation-nominated.node.invitePending.transition.accept.description=\u627f\u8afe +inwf_invitation-nominated.node.invitePending.transition.cancel.title=\u30ad\u30e3\u30f3\u30bb\u30eb +inwf_invitation-nominated.node.invitePending.transition.cancel.description=\u30ad\u30e3\u30f3\u30bb\u30eb + +inwf_invitation-nominated.node.inviteRejected.title=\u5374\u4e0b\u6e08 +inwf_invitation-nominated.node.inviteRejected.description=\u5374\u4e0b\u6e08 +inwf_invitation-nominated.node.inviteRejected.transition.end.title=\u30bf\u30b9\u30af\u5b8c\u4e86 +inwf_invitation-nominated.node.inviteRejected.transition.end.description=\u30bf\u30b9\u30af\u5b8c\u4e86 + +inwf_invitation-nominated.node.inviteAccepted.title=\u627f\u8afe\u6e08 +inwf_invitation-nominated.node.inviteAccepted.description=\u627f\u8afe\u6e08 +inwf_invitation-nominated.node.inviteAccepted.transition.end.title=\u30bf\u30b9\u30af\u5b8c\u4e86 +inwf_invitation-nominated.node.inviteAccepted.transition.end.description=\u30bf\u30b9\u30af\u5b8c\u4e86 + +inwf_invitation-nominated.node.end.title=\u7d42\u4e86 +inwf_invitation-nominated.node.end.description=\u7d42\u4e86 diff --git a/config/alfresco/workflow/wcm-workflow-messages_de.properties b/config/alfresco/workflow/wcm-workflow-messages_de.properties index 27b2870e60..31f0efc998 100755 --- a/config/alfresco/workflow/wcm-workflow-messages_de.properties +++ b/config/alfresco/workflow/wcm-workflow-messages_de.properties @@ -4,7 +4,7 @@ # Submit Workflow # -wcmwf_submit.workflow.title=Website Vorlage +wcmwf_submit.workflow.title=Site Vorlage wcmwf_submit.workflow.description=\u00c4nderungen zur Zustimmung vorlegen wcmwf_submit.node.verifybrokenlinks.transition.abort.title=Vorlage abbrechen wcmwf_submit.node.verifybrokenlinks.transition.abort.description=Vorlage abbrechen @@ -27,15 +27,15 @@ wcmwf_submit.node.submitpending.transition.cancel.description=Vorlage abbrechen wcmwf_submit.node.submitpending.transition.launch.title=Jetzt vorlegen wcmwf_submit.node.submitpending.transition.launch.description=Jetzt vorlegen -wcmwf_submitdirect.workflow.title=Website Vorlage (direkt) +wcmwf_submitdirect.workflow.title=Site Vorlage (direkt) wcmwf_submitdirect.workflow.description=Der Staging Sandbox die \u00c4nderungen direkt vorlegen # Submit Task Definitions -wcmwf_workflowmodel.type.wcmwf_submitReviewTask.title=Website Vorlage +wcmwf_workflowmodel.type.wcmwf_submitReviewTask.title=Site Vorlage wcmwf_workflowmodel.type.wcmwf_submitReviewTask.description=\u00c4nderungen zur Zustimmung vorlegen -wcmwf_workflowmodel.type.wcmwf_submitDirectTask.title=Website Vorlage (direkt) +wcmwf_workflowmodel.type.wcmwf_submitDirectTask.title=Site Vorlage (direkt) wcmwf_workflowmodel.type.wcmwf_submitDirectTask.description=Der Staging Sandbox die \u00c4nderungen direkt vorlegen wcmwf_workflowmodel.type.wcmwf_verifyBrokenLinksTask.title=Kaputte Links pr\u00fcfen wcmwf_workflowmodel.type.wcmwf_verifyBrokenLinksTask.description=Links pr\u00fcfen, die nicht zu einer aktiven Ressource f\u00fchren diff --git a/config/alfresco/workflow/wcm-workflow-messages_fr.properties b/config/alfresco/workflow/wcm-workflow-messages_fr.properties index 497a451ff8..5a81fd5d50 100755 --- a/config/alfresco/workflow/wcm-workflow-messages_fr.properties +++ b/config/alfresco/workflow/wcm-workflow-messages_fr.properties @@ -64,14 +64,14 @@ wcmwf_workflowmodel.property.wcmwf_submitReviewType.title=Type de r\u00e9vision wcmwf_workflowmodel.property.wcmwf_submitReviewType.description=R\u00e9vision en s\u00e9rie ou en parall\u00e8le wcmwf_workflowmodel.property.wcmwf_fromPath.title=Chemin du dossier source wcmwf_workflowmodel.property.wcmwf_fromPath.description=Chemin d'acc\u00e8s au dossier \u00e0 partir duquel les \u00e9l\u00e9ments ont \u00e9t\u00e9 soumis -wcmwf_workflowmodel.property.wcmwf_label.title=\u00c9tiquette de soumission -wcmwf_workflowmodel.property.wcmwf_label.description=\u00c9tiquette associ\u00e9e \u00e0 la soumission +wcmwf_workflowmodel.property.wcmwf_label.title=Tag de soumission +wcmwf_workflowmodel.property.wcmwf_label.description=Tag associ\u00e9 \u00e0 la soumission wcmwf_workflowmodel.property.wcmwf_launchDate.title=Date de lancement wcmwf_workflowmodel.property.wcmwf_launchDate.description=Date \u00e0 laquelle le contenu de la soumission doit \u00eatre valid\u00e9 wcmwf_workflowmodel.property.wcmwf_autoDeploy.title=D\u00e9ploiement automatique wcmwf_workflowmodel.property.wcmwf_autoDeploy.description=Indique si les modifications soumises doivent \u00eatre d\u00e9ploy\u00e9es une fois approuv\u00e9es wcmwf_workflowmodel.property.wcmwf_webapp.title=WebApp -wcmwf_workflowmodel.property.wcmwf_webapp.description=Application Web dans la base des flux de travail dont les liens doivent \u00eatre v\u00e9rifi\u00e9s +wcmwf_workflowmodel.property.wcmwf_webapp.description=Application Web dans la base des workflows dont les liens doivent \u00eatre v\u00e9rifi\u00e9s wcmwf_workflowmodel.property.wcmwf_reviewerCnt.title=Total r\u00e9vision wcmwf_workflowmodel.property.wcmwf_reviewerCnt.description=Nombre de personnes qui ont r\u00e9vis\u00e9 wcmwf_workflowmodel.property.wcmwf_approveCnt.title=Total approuv\u00e9 diff --git a/config/alfresco/workflow/wcm-workflow-messages_ja.properties b/config/alfresco/workflow/wcm-workflow-messages_ja.properties new file mode 100755 index 0000000000..de5f013b35 --- /dev/null +++ b/config/alfresco/workflow/wcm-workflow-messages_ja.properties @@ -0,0 +1,90 @@ +# Display labels for out-of-the-box WCM Content-oriented Workflows + +# +# Submit Workflow +# + +wcmwf_submit.workflow.title=Web\u30fb\u30b5\u30a4\u30c8\u63d0\u51fa +wcmwf_submit.workflow.description=\u627f\u8a8d\u7528\u306b\u5909\u66f4\u3092\u9001\u4fe1 +wcmwf_submit.node.verifybrokenlinks.transition.abort.title=\u63d0\u51fa\u306e\u505c\u6b62 +wcmwf_submit.node.verifybrokenlinks.transition.abort.description=\u63d0\u51fa\u306e\u505c\u6b62 +wcmwf_submit.node.verifybrokenlinks.transition.continue.title=\u63d0\u51fa\u3092\u7d99\u7d9a +wcmwf_submit.node.verifybrokenlinks.transition.continue.description=\u63d0\u51fa\u3092\u7d99\u7d9a +wcmwf_submit.node.serialreview.transition.reject.title=\u5374\u4e0b +wcmwf_submit.node.serialreview.transition.reject.description=\u5374\u4e0b +wcmwf_submit.node.serialreview.transition.approve.title=\u627f\u8a8d +wcmwf_submit.node.serialreview.transition.approve.description=\u627f\u8a8d +wcmwf_submit.node.parallelreview.transition.reject.title=\u5374\u4e0b +wcmwf_submit.node.parallelreview.transition.reject.description=\u5374\u4e0b +wcmwf_submit.node.parallelreview.transition.approve.title=\u627f\u8a8d +wcmwf_submit.node.parallelreview.transition.approve.description=\u627f\u8a8d +wcmwf_submit.node.rejected.transition.abort.title=\u30ec\u30d3\u30e5\u30fc\u306e\u505c\u6b62 +wcmwf_submit.node.rejected.transition.abort.description=\u30ec\u30d3\u30e5\u30fc\u306e\u505c\u6b62 +wcmwf_submit.node.rejected.transition.resubmit.title=\u30ec\u30d3\u30e5\u30fc\u306e\u518d\u9001\u4fe1 +wcmwf_submit.node.rejected.transition.resubmit.description=\u30ec\u30d3\u30e5\u30fc\u306e\u518d\u9001\u4fe1 +wcmwf_submit.node.submitpending.transition.cancel.title=\u63d0\u51fa\u306e\u505c\u6b62 +wcmwf_submit.node.submitpending.transition.cancel.description=\u63d0\u51fa\u306e\u505c\u6b62 +wcmwf_submit.node.submitpending.transition.launch.title=\u4eca\u3059\u3050\u9001\u4fe1 +wcmwf_submit.node.submitpending.transition.launch.description=\u4eca\u3059\u3050\u9001\u4fe1 + +wcmwf_submitdirect.workflow.title=Web\u30fb\u30b5\u30a4\u30c8\u63d0\u51fa\uff08\u76f4\u63a5\uff09 +wcmwf_submitdirect.workflow.description=\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u30fb\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u306b\u5909\u66f4\u3092\u76f4\u63a5\u9001\u4fe1 + + +# Submit Task Definitions + +wcmwf_workflowmodel.type.wcmwf_submitReviewTask.title=Web\u30b5\u30a4\u30c8\u63d0\u51fa +wcmwf_workflowmodel.type.wcmwf_submitReviewTask.description=\u627f\u8a8d\u7528\u306b\u5909\u66f4\u3092\u9001\u4fe1 +wcmwf_workflowmodel.type.wcmwf_submitDirectTask.title=Web\u30b5\u30a4\u30c8\u63d0\u51fa\uff08\u76f4\u63a5\uff09 +wcmwf_workflowmodel.type.wcmwf_submitDirectTask.description=\u30b9\u30c6\u30fc\u30b8\u30f3\u30b0\u30b5\u30f3\u30c9\u30dc\u30c3\u30af\u30b9\u306b\u5909\u66f4\u3092\u76f4\u63a5\u9001\u4fe1 +wcmwf_workflowmodel.type.wcmwf_verifyBrokenLinksTask.title=\u30ea\u30f3\u30af\u5207\u308c\u3092\u78ba\u8a8d\u3059\u308b +wcmwf_workflowmodel.type.wcmwf_verifyBrokenLinksTask.description=\u30e9\u30a4\u30d6\u30ea\u30bd\u30fc\u30b9\u3092\u6307\u3057\u3066\u3044\u306a\u3044\u30ea\u30f3\u30af\u3092\u78ba\u8a8d\u3059\u308b +wcmwf_workflowmodel.type.wcmwf_reviewTask.title=\u30ec\u30d3\u30e5\u30fc +wcmwf_workflowmodel.type.wcmwf_reviewTask.description=\u6587\u66f8\u3092\u30ec\u30d3\u30e5\u30fc\u3057\u305d\u308c\u3092\u627f\u8a8d\u307e\u305f\u306f\u5374\u4e0b\u3059\u308b +wcmwf_workflowmodel.type.wcmwf_parallelReviewTask.title=\u30ec\u30d3\u30e5\u30fc +wcmwf_workflowmodel.type.wcmwf_parallelReviewTask.description=\u6587\u66f8\u3092\u30ec\u30d3\u30e5\u30fc\u3057\u305d\u308c\u3092\u627f\u8a8d\u307e\u305f\u306f\u5374\u4e0b\u3059\u308b +wcmwf_workflowmodel.type.wcmwf_rejectedTask.title=\u5374\u4e0b\u6e08 +wcmwf_workflowmodel.type.wcmwf_rejectedTask.description=\u5374\u4e0b\u6e08 +wcmwf_workflowmodel.type.wcmwf_approvedTask.title=\u627f\u8a8d\u6e08 +wcmwf_workflowmodel.type.wcmwf_approvedTask.description=\u627f\u8a8d\u6e08 +wcmwf_workflowmodel.type.wcmwf_submittedTask.title=\u63d0\u51fa\u6e08 +wcmwf_workflowmodel.type.wcmwf_submittedTask.description=\u63d0\u51fa\u6e08 +wcmwf_workflowmodel.type.wcmwf_submittedDirectTask.title=\u63d0\u51fa\u6e08 +wcmwf_workflowmodel.type.wcmwf_submittedDirectTask.description=\u63d0\u51fa\u6e08 +wcmwf_workflowmodel.type.wcmwf_submitpendingTask.title=\u63d0\u51fa\u306e\u4fdd\u7559 +wcmwf_workflowmodel.type.wcmwf_submitpendingTask.description=\u63d0\u51fa\u306e\u4fdd\u7559 +wcmwf_workflowmodel.type.wcmwf_submitcancelledTask.title=\u63d0\u51fa\u306e\u505c\u6b62 +wcmwf_workflowmodel.type.wcmwf_submitcancelledTask.description=\u63d0\u51fa\u306e\u505c\u6b62 +wcmwf_workflowmodel.type.wcmwf_submitfailedTask.title=\u63d0\u51fa\u306e\u5931\u6557 +wcmwf_workflowmodel.type.wcmwf_submitfailedTask.description=\u63d0\u51fa\u306e\u5931\u6557 + +wcmwf_workflowmodel.property.wcmwf_reviewType.title=\u30ec\u30d3\u30e5\u30fc\u30bf\u30a4\u30d7 +wcmwf_workflowmodel.property.wcmwf_reviewType.description=\u9806\u6b21\u307e\u305f\u306f\u4e26\u5217\u30ec\u30d3\u30e5\u30fc +wcmwf_workflowmodel.property.wcmwf_submitReviewType.title=\u30ec\u30d3\u30e5\u30fc\u30bf\u30a4\u30d7 +wcmwf_workflowmodel.property.wcmwf_submitReviewType.description=\u9806\u6b21\u307e\u305f\u306f\u4e26\u5217\u30ec\u30d3\u30e5\u30fc +wcmwf_workflowmodel.property.wcmwf_fromPath.title=\u30bd\u30fc\u30b9\u30fb\u30d5\u30a9\u30eb\u30c0\u30fb\u30d1\u30b9 +wcmwf_workflowmodel.property.wcmwf_fromPath.description=\u9805\u76ee\u306e\u9001\u4fe1\u5143\u30d5\u30a9\u30eb\u30c0\u30fb\u30d1\u30b9 +wcmwf_workflowmodel.property.wcmwf_label.title=\u63d0\u51fa\u30e9\u30d9\u30eb +wcmwf_workflowmodel.property.wcmwf_label.description=\u63d0\u51fa\u306b\u95a2\u9023\u4ed8\u3044\u305f\u30e9\u30d9\u30eb +wcmwf_workflowmodel.property.wcmwf_launchDate.title=\u8d77\u52d5\u65e5 +wcmwf_workflowmodel.property.wcmwf_launchDate.description=\u63d0\u51fa\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u3092\u59d4\u4efb\u3059\u308b\u65e5\u4ed8\u3092\u4ed8\u3051\u308b +wcmwf_workflowmodel.property.wcmwf_autoDeploy.title=\u81ea\u52d5\u30c7\u30d7\u30ed\u30a4 +wcmwf_workflowmodel.property.wcmwf_autoDeploy.description=\u9001\u4fe1\u3055\u308c\u305f\u5909\u66f4\u3092\u627f\u8a8d\u6642\u306b\u30c7\u30d7\u30ed\u30a4\u3059\u308b\u304b\u3069\u3046\u304b +wcmwf_workflowmodel.property.wcmwf_webapp.title=Webapp +wcmwf_workflowmodel.property.wcmwf_webapp.description=\u30ea\u30f3\u30af\u3092\u30c1\u30a7\u30c3\u30af\u3059\u308b\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30b9\u30c8\u30a2\u5185\u90e8\u306ewebapp +wcmwf_workflowmodel.property.wcmwf_reviewerCnt.title=\u30ec\u30d3\u30e5\u30fc\u306e\u5408\u8a08 +wcmwf_workflowmodel.property.wcmwf_reviewerCnt.description=\u30ec\u30d3\u30e5\u30fc\u3055\u308c\u305f\u30e6\u30fc\u30b6\u30fc\u306e\u30ab\u30a6\u30f3\u30c8 +wcmwf_workflowmodel.property.wcmwf_approveCnt.title=\u627f\u8a8d\u6e08\u306e\u5408\u8a08 +wcmwf_workflowmodel.property.wcmwf_approveCnt.description=\u627f\u8a8d\u6e08\u306e\u30e6\u30fc\u30b6\u30fc\u306e\u30ab\u30a6\u30f3\u30c8 + +# Change Request Workflow + +wcmwf_changerequest.workflow.title=\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u5909\u66f4 +wcmwf_changerequest.workflow.description=\u5909\u66f4\u306e\u30a2\u30bb\u30c3\u30c8\u3092\u5272\u308a\u5f53\u3066\u308b + +# Change Request Task Definitions + +wcmwf_workflowmodel.type.wcmwf_submitChangeRequestTask.title=\u5909\u66f4\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u63d0\u51fa +wcmwf_workflowmodel.type.wcmwf_submitChangeRequestTask.description=1\u3064\u4ee5\u4e0a\u306e\u9805\u76ee\u306e\u5909\u66f4\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u63d0\u51fa +wcmwf_workflowmodel.type.wcmwf_changeRequestTask.title=\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u5909\u66f4 +wcmwf_workflowmodel.type.wcmwf_changeRequestTask.description=\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u5909\u66f4 diff --git a/config/alfresco/workflow/workflow-messages_fr.properties b/config/alfresco/workflow/workflow-messages_fr.properties index d614de669d..2b35208cb3 100755 --- a/config/alfresco/workflow/workflow-messages_fr.properties +++ b/config/alfresco/workflow/workflow-messages_fr.properties @@ -52,17 +52,17 @@ wf_parallelreview.workflow.description=R\u00e9vision & approbation en parall\u00 wf_workflowmodel.type.wf_submitParallelReviewTask.title=D\u00e9marrer la r\u00e9vision en parall\u00e8le wf_workflowmodel.type.wf_submitParallelReviewTask.description=Soumettre des documents pour r\u00e9vision & approbation \u00e0 une liste de personnes wf_workflowmodel.property.wf_requiredApprovePercent.title=Pourcentage d'approbation requis -wf_workflowmodel.property.wf_requiredApprovePercent.description=Pourcentage de r\u00e9viseurs qui doivent approuver le contenu pour qu'il soit approuv\u00e9 +wf_workflowmodel.property.wf_requiredApprovePercent.description=Pourcentage de relecteurs qui doivent approuver le contenu pour qu'il soit approuv\u00e9 wf_workflowmodel.type.wf_rejectedParallelTask.title=Rejet\u00e9 wf_workflowmodel.type.wf_rejectedParallelTask.description=Rejet\u00e9 wf_workflowmodel.type.wf_approvedParallelTask.title=Approuv\u00e9 wf_workflowmodel.type.wf_approvedParallelTask.description=Approuv\u00e9 -wf_workflowmodel.property.wf_reviewerCount.title=Nombre de r\u00e9viseurs -wf_workflowmodel.property.wf_reviewerCount.description=Nombre de r\u00e9viseurs +wf_workflowmodel.property.wf_reviewerCount.title=Nombre de relecteurs +wf_workflowmodel.property.wf_reviewerCount.description=Nombre de relecteurs wf_workflowmodel.property.wf_requiredPercent.title=Pourcentage d'approbation requis wf_workflowmodel.property.wf_requiredPercent.description=Pourcentage d'approbation requis -wf_workflowmodel.property.wf_approveCount.title=R\u00e9viseurs qui ont approuv\u00e9 -wf_workflowmodel.property.wf_approveCount.description=R\u00e9viseurs qui ont approuv\u00e9 +wf_workflowmodel.property.wf_approveCount.title=Relecteurs qui ont approuv\u00e9 +wf_workflowmodel.property.wf_approveCount.description=Relecteurs qui ont approuv\u00e9 wf_workflowmodel.property.wf_actualPercent.title=Pourcentage d'approbation exact wf_workflowmodel.property.wf_actualPercentdescription=Pourcentage d'approbation exact @@ -115,7 +115,7 @@ wf_adhoc.workflow.description=Assigner une t\u00e2che \u00e0 un coll\u00e8gue wf_workflowmodel.type.wf_submitAdhocTask.title=D\u00e9marrer une t\u00e2che adhoc wf_workflowmodel.type.wf_submitAdhocTask.description=Allouer une t\u00e2che \u00e0 un coll\u00e8gue wf_workflowmodel.property.wf_notifyMe.title=M'avertir -wf_workflowmodel.property.wf_notifyMe.description=M'avertir quand la t\u00e2che est termin\u00e9e +wf_workflowmodel.property.wf_notifyMe.description=M'avertir quand la t\u00e2che est achev\u00e9e wf_workflowmodel.type.wf_adhocTask.title=T\u00e2che Adhoc wf_workflowmodel.type.wf_adhocTask.description=T\u00e2che adhoc allou\u00e9e par un coll\u00e8gue wf_workflowmodel.type.wf_completedAdhocTask.title=T\u00e2che adhoc termin\u00e9e diff --git a/config/alfresco/workflow/workflow-messages_ja.properties b/config/alfresco/workflow/workflow-messages_ja.properties new file mode 100755 index 0000000000..66b77d8d18 --- /dev/null +++ b/config/alfresco/workflow/workflow-messages_ja.properties @@ -0,0 +1,122 @@ +# Display labels for out-of-the-box Content-oriented Workflows + +# +# Review & Approve Workflow +# + +wf_review.workflow.title=\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d +wf_review.workflow.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d + +# Review & Approve Task Definitions + +wf_workflowmodel.type.wf_submitReviewTask.title=\u30ec\u30d3\u30e5\u30fc\u306e\u958b\u59cb +wf_workflowmodel.type.wf_submitReviewTask.description=\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\u7528\u306b\u6587\u66f8\u3092\u9001\u4fe1 +wf_workflowmodel.type.wf_reviewTask.title=\u30ec\u30d3\u30e5\u30fc +wf_workflowmodel.type.wf_reviewTask.description=\u6587\u66f8\u3092\u30ec\u30d3\u30e5\u30fc\u3057\u305d\u308c\u3092\u627f\u8a8d\u307e\u305f\u306f\u5374\u4e0b\u3059\u308b +wf_workflowmodel.type.wf_rejectedTask.title=\u5374\u4e0b\u6e08 +wf_workflowmodel.type.wf_rejectedTask.description=\u5374\u4e0b\u6e08 +wf_workflowmodel.type.wf_approvedTask.title=\u627f\u8a8d\u6e08 +wf_workflowmodel.type.wf_approvedTask.description=\u627f\u8a8d\u6e08 + +# Review & Approve Process Definitions + +wf_review.node.start.title=\u958b\u59cb +wf_review.node.start.description=\u958b\u59cb +wf_review.node.review.title=\u30ec\u30d3\u30e5\u30fc +wf_review.node.review.description=\u30ec\u30d3\u30e5\u30fc +wf_review.node.review.transition.reject.title=\u5374\u4e0b +wf_review.node.review.transition.reject.description=\u5374\u4e0b +wf_review.node.review.transition.approve.title=\u627f\u8a8d +wf_review.node.review.transition.approve.description=\u627f\u8a8d +wf_review.node.rejected.title=\u5374\u4e0b\u6e08 +wf_review.node.rejected.description=\u5374\u4e0b\u6e08 +wf_review.task.wf_rejectedTask.title=\u5374\u4e0b\u6e08 +wf_review.task.wf_rejectedTask.description=\u5374\u4e0b\u6e08 +wf_review.node.approved.title=\u627f\u8a8d\u6e08 +wf_review.node.approved.description=\u627f\u8a8d\u6e08 +wf_review.task.wf_approvedTask.title=\u627f\u8a8d\u6e08 +wf_review.task.wf_approvedTask.description=\u627f\u8a8d\u6e08 +wf_review.node.end.title=\u7d42\u4e86 +wf_review.node.end.description=\u7d42\u4e86 + + +# +# Parallel Review Workflow +# + +wf_parallelreview.workflow.title=\u4e26\u5217\u306e\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d +wf_parallelreview.workflow.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u4e26\u5217\u306e\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d + +# Parallel Review & Approve Task Definitions + +wf_workflowmodel.type.wf_submitParallelReviewTask.title=\u4e26\u5217\u306e\u30ec\u30d3\u30e5\u30fc\u306e\u958b\u59cb +wf_workflowmodel.type.wf_submitParallelReviewTask.description=\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\u7528\u306b\u6587\u66f8\u3092\u30e6\u30fc\u30b6\u30fc\u306e\u30ea\u30b9\u30c8\u306b\u9001\u4fe1 +wf_workflowmodel.property.wf_requiredApprovePercent.title=\u8981\u6c42\u3055\u308c\u305f\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +wf_workflowmodel.property.wf_requiredApprovePercent.description=\u627f\u8a8d\u3092\u53d7\u3051\u308b\u305f\u3081\u306b\u627f\u8a8d\u3059\u308b\u5fc5\u8981\u306e\u3042\u308b\u30ec\u30d3\u30e5\u30a2\u306e\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +wf_workflowmodel.type.wf_rejectedParallelTask.title=\u5374\u4e0b\u6e08 +wf_workflowmodel.type.wf_rejectedParallelTask.description=\u5374\u4e0b\u6e08 +wf_workflowmodel.type.wf_approvedParallelTask.title=\u627f\u8a8d\u6e08 +wf_workflowmodel.type.wf_approvedParallelTask.description=\u627f\u8a8d\u6e08 +wf_workflowmodel.property.wf_reviewerCount.title=\u30ec\u30d3\u30e5\u30a2\u306e\u6570 +wf_workflowmodel.property.wf_reviewerCount.description=\u30ec\u30d3\u30e5\u30a2\u306e\u6570 +wf_workflowmodel.property.wf_requiredPercent.title=\u8981\u6c42\u3055\u308c\u305f\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +wf_workflowmodel.property.wf_requiredPercent.description=\u8981\u6c42\u3055\u308c\u305f\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +wf_workflowmodel.property.wf_approveCount.title=\u627f\u8a8d\u3055\u308c\u305f\u30ec\u30d3\u30e5\u30a2 +wf_workflowmodel.property.wf_approveCount.description=\u627f\u8a8d\u3055\u308c\u305f\u30ec\u30d3\u30e5\u30a2 +wf_workflowmodel.property.wf_actualPercent.title=\u5b9f\u969b\u306e\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +wf_workflowmodel.property.wf_actualPercentdescription=\u5b9f\u969b\u306e\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 + +# Group Review & Approve Process Definitions + +wf_parallelreview.node.review.transition.reject.title=\u5374\u4e0b +wf_parallelreview.node.review.transition.reject.description=\u5374\u4e0b +wf_parallelreview.node.review.transition.approve.title=\u627f\u8a8d +wf_parallelreview.node.review.transition.approve.description=\u627f\u8a8d + + +# +# Pooled Review Workflow +# + +wf_reviewpooled.workflow.title=\u30d7\u30fc\u30eb\u3055\u308c\u305f\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d +wf_reviewpooled.workflow.description=\u30d7\u30fc\u30eb\u3055\u308c\u305f\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d + +wf_workflowmodel.type.wf_submitGroupReviewTask.title=\u30b0\u30eb\u30fc\u30d7\u30ec\u30d3\u30e5\u30fc\u306e\u958b\u59cb +wf_workflowmodel.type.wf_submitGroupReviewTask.description=\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\u7528\u306b\u6587\u66f8\u3092\u30e6\u30fc\u30b6\u30fc\u30b0\u30eb\u30fc\u30d7\u306b\u9001\u4fe1 + +wf_reviewpooled.node.review.transition.reject.title=\u5374\u4e0b +wf_reviewpooled.node.review.transition.reject.description=\u5374\u4e0b +wf_reviewpooled.node.review.transition.approve.title=\u627f\u8a8d +wf_reviewpooled.node.review.transition.approve.description=\u627f\u8a8d + + +# +# Parallel Group Review Workflow +# + +wf_parallelgroupreview.workflow.title=\u30b0\u30eb\u30fc\u30d7\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d +wf_parallelgroupreview.workflow.description=\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30b0\u30eb\u30fc\u30d7\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d + +wf_parallelgroupreview.node.review.transition.reject.title=\u5374\u4e0b +wf_parallelgroupreview.node.review.transition.reject.description=\u5374\u4e0b +wf_parallelgroupreview.node.review.transition.approve.title=\u627f\u8a8d +wf_parallelgroupreview.node.review.transition.approve.description=\u627f\u8a8d + + +# +# Adhoc Task Workflow +# + +wf_adhoc.workflow.title=\u30a2\u30c9\u30db\u30c3\u30af +wf_adhoc.workflow.description=\u540c\u50da\u306b\u30bf\u30b9\u30af\u3092\u5272\u308a\u5f53\u3066\u308b + +# Adhoc Task Definitions + +wf_workflowmodel.type.wf_submitAdhocTask.title=\u30a2\u30c9\u30db\u30c3\u30af\u30bf\u30b9\u30af\u306e\u958b\u59cb +wf_workflowmodel.type.wf_submitAdhocTask.description=\u540c\u50da\u306b\u30bf\u30b9\u30af\u3092\u5272\u308a\u5f53\u3066\u308b +wf_workflowmodel.property.wf_notifyMe.title=\u81ea\u5206\u306b\u901a\u77e5\u3059\u308b +wf_workflowmodel.property.wf_notifyMe.description=\u30bf\u30b9\u30af\u304c\u5b8c\u4e86\u3057\u305f\u3089\u81ea\u5206\u306b\u901a\u77e5\u3059\u308b +wf_workflowmodel.type.wf_adhocTask.title=\u30a2\u30c9\u30db\u30c3\u30af\u30fb\u30bf\u30b9\u30af +wf_workflowmodel.type.wf_adhocTask.description=\u540c\u50da\u306b\u5272\u308a\u5f53\u3066\u3089\u308c\u305f\u30a2\u30c9\u30db\u30c3\u30af\u30fb\u30bf\u30b9\u30af +wf_workflowmodel.type.wf_completedAdhocTask.title=\u5b8c\u4e86\u6e08\u30a2\u30c9\u30db\u30c3\u30af\u30fb\u30bf\u30b9\u30af +wf_workflowmodel.type.wf_completedAdhocTask.description=\u5b8c\u4e86\u6e08\u30a2\u30c9\u30db\u30c3\u30af\u30fb\u30bf\u30b9\u30af diff --git a/source/java/org/alfresco/filesys/repo/CifsHelper.java b/source/java/org/alfresco/filesys/repo/CifsHelper.java index 874b7c0874..e5c5dc38a7 100644 --- a/source/java/org/alfresco/filesys/repo/CifsHelper.java +++ b/source/java/org/alfresco/filesys/repo/CifsHelper.java @@ -609,7 +609,7 @@ public class CifsHelper { try { - fileFolderService.move(nodeToMoveRef, oldParent, newParentNodeRef, newName); + fileFolderService.moveFrom(nodeToMoveRef, oldParent, newParentNodeRef, newName); } catch (org.alfresco.service.cmr.model.FileExistsException e) { diff --git a/source/java/org/alfresco/filesys/repo/ContentContext.java b/source/java/org/alfresco/filesys/repo/ContentContext.java index a9337cf4b5..630dfd0969 100644 --- a/source/java/org/alfresco/filesys/repo/ContentContext.java +++ b/source/java/org/alfresco/filesys/repo/ContentContext.java @@ -18,6 +18,8 @@ */ package org.alfresco.filesys.repo; +import java.util.regex.Pattern; + import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.alfresco.AlfrescoContext; import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; @@ -75,6 +77,8 @@ public class ContentContext extends AlfrescoContext private ThreadRequestPool m_threadPool; + private Pattern renameShufflePattern = Pattern.compile("(.*\\.tmp)|(.*\\.wbk)|(.*\\.bak)|(.*\\~)"); + /** * Default constructor allowing initialization by container. */ @@ -163,7 +167,32 @@ public class ContentContext extends AlfrescoContext public void setDisableOplocks( boolean disableOplocks) { m_oplocksDisabled = disableOplocks; } - + + /** + * Get the regular expression pattern that will be applied to detected potential + * rename shuffles. + * + * @return the regular expression pattern to match against + */ + public Pattern getRenameShufflePattern() + { + return renameShufflePattern; + } + + /** + * Set the regular expression that will be applied to filenames during renames + * to detect whether clients are performing a renaming shuffle - common during + * file saving on various clients. + *

+ * ALF-3856 + * + * @param renameShufflePattern a regular expression filename match + */ + public void setRenameShufflePattern(Pattern renameShufflePattern) + { + this.renameShufflePattern = renameShufflePattern; + } + @Override public void initialize(AlfrescoDiskDriver filesysDriver) { diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java index 46bea63031..709c9bdadb 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Callable; +import java.util.regex.Pattern; import javax.transaction.UserTransaction; @@ -184,15 +185,9 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa private SysAdminParams sysAdminParams; private BehaviourFilter policyBehaviourFilter; - - // Node monitor factory - private NodeMonitorFactory m_nodeMonitorFactory; - - // Lock manager - private static FileStateLockManager _lockManager; - + /** * Class constructor * @@ -2439,190 +2434,248 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa } } - // Perform repository updates in a retryable write transaction - final FileState finalFileState = toUpdate; - Pair result = doInWriteTransaction(sess, new CallableIO>() + // Depending on whether the node has the NO_CONTENT aspect, we may have to wipe it out on error + final CallableIO errorHandler = new CallableIO() { - public Pair call() throws IOException + public Void call() throws IOException { - // Check if the file is an OpenOffice document and hte truncation flag is set - // - // Note: Check before the timestamp update - - if ( file instanceof OpenOfficeContentNetworkFile) { - OpenOfficeContentNetworkFile ooFile = (OpenOfficeContentNetworkFile) file; - if ( ooFile.truncatedToZeroLength()) { - - // Inhibit versioning for this transaction - - getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE); - - // Debug - - if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.debug("OpenOffice file truncation update only, inhibit versioning, " + file.getFullName()); - } - } - - // Update the modification date on the file/folder node - if (finalFileState != null && file instanceof ContentNetworkFile) + if (file instanceof NodeRefNetworkFile) { - NodeRef nodeRef = (NodeRef) finalFileState.getFilesystemObject(); - - // Check if the file data has been updated, if not then inhibit versioning for this txn - // so the timestamp update does not generate a new file version - - ContentNetworkFile contentFile = (ContentNetworkFile) file; - if ( contentFile.isModified() == false && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) { - - // Stop a new file version being generated - - getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE); - - // Debug - - if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.debug("Timestamp update only, inhibit versioning, " + file.getFullName()); - } - - // Update the modification timestamp - - getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); - - if (permissionService.hasPermission((NodeRef) finalFileState.getFilesystemObject(), PermissionService.WRITE_PROPERTIES) == AccessStatus.ALLOWED) - { - Date modifyDate = new Date(finalFileState.getModifyDateTime()); - nodeService.setProperty(nodeRef, ContentModel.PROP_MODIFIED, modifyDate); - - // Debug - - if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.debug("Updated modification timestamp, " + file.getFullName() + ", modTime=" + modifyDate); - } - } - - // Defer to the network file to close the stream and remove the content - - file.closeFile(); - - // Remove the node if marked for delete - - if (file.hasDeleteOnClose()) - { - // Check if the file is a noderef based file - - if ( file instanceof NodeRefNetworkFile) + NodeRef nodeRef = ((NodeRefNetworkFile) file).getNodeRef(); + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT)) { - NodeRefNetworkFile nodeNetFile = (NodeRefNetworkFile) file; - NodeRef nodeRef = nodeNetFile.getNodeRef(); + fileFolderService.delete(nodeRef); + } + } + + return null; + } + }; + try + { + // Perform repository updates in a retryable write transaction + final FileState finalFileState = toUpdate; + Pair result = doInWriteTransaction(sess, new CallableIO>() + { + public Pair call() throws IOException + { + // Check if the file is an OpenOffice document and hte truncation flag is set + // + // Note: Check before the timestamp update + + if ( file instanceof OpenOfficeContentNetworkFile) { + OpenOfficeContentNetworkFile ooFile = (OpenOfficeContentNetworkFile) file; + if ( ooFile.truncatedToZeroLength()) { + + // Inhibit versioning for this transaction + + getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE); + + // Debug + + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.debug("OpenOffice file truncation update only, inhibit versioning, " + file.getFullName()); + } + } + + // Update the modification date on the file/folder node + if (finalFileState != null && file instanceof ContentNetworkFile) + { + NodeRef nodeRef = (NodeRef) finalFileState.getFilesystemObject(); + + // Check if the file data has been updated, if not then inhibit versioning for this txn + // so the timestamp update does not generate a new file version - // We don't know how long the network file has had the reference, so check for existence + ContentNetworkFile contentFile = (ContentNetworkFile) file; + if ( contentFile.isModified() == false && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) { + + // Stop a new file version being generated + + getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE); + + // Debug + + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.debug("Timestamp update only, inhibit versioning, " + file.getFullName()); + } + + // Update the modification timestamp - if (fileFolderService.exists(nodeRef)) + getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + + if (permissionService.hasPermission((NodeRef) finalFileState.getFilesystemObject(), PermissionService.WRITE_PROPERTIES) == AccessStatus.ALLOWED) + { + Date modifyDate = new Date(finalFileState.getModifyDateTime()); + nodeService.setProperty(nodeRef, ContentModel.PROP_MODIFIED, modifyDate); + + // Debug + + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.debug("Updated modification timestamp, " + file.getFullName() + ", modTime=" + modifyDate); + } + } + + // Defer to the network file to close the stream and remove the content + + file.closeFile(); + + // Remove the node if marked for delete + + if (file.hasDeleteOnClose()) + { + // Check if the file is a noderef based file + + if ( file instanceof NodeRefNetworkFile) { - try + NodeRefNetworkFile nodeNetFile = (NodeRefNetworkFile) file; + NodeRef nodeRef = nodeNetFile.getNodeRef(); + + // We don't know how long the network file has had the reference, so check for existence + + if (fileFolderService.exists(nodeRef)) { - boolean isVersionable = nodeService.hasAspect( nodeRef, ContentModel.ASPECT_VERSIONABLE); - try { - // Delete the file - - fileFolderService.delete(nodeRef); - - } - catch ( Exception ex) - { - // Propagate retryable errors. Log the rest. - if (RetryingTransactionHelper.extractRetryCause(ex) != null) + boolean isVersionable = nodeService.hasAspect( nodeRef, ContentModel.ASPECT_VERSIONABLE); + + try { - if (ex instanceof RuntimeException) - { - throw (RuntimeException)ex; - } - else - { - throw new AlfrescoRuntimeException("Error during delete on close, " + file.getFullName(), ex); - } + // Delete the file + + fileFolderService.delete(nodeRef); + } - if ( logger.isWarnEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.warn("Error during delete on close, " + file.getFullName(), ex); + catch ( Exception ex) + { + // Propagate retryable errors. Log the rest. + if (RetryingTransactionHelper.extractRetryCause(ex) != null) + { + if (ex instanceof RuntimeException) + { + throw (RuntimeException)ex; + } + else + { + throw new AlfrescoRuntimeException("Error during delete on close, " + file.getFullName(), ex); + } + } + if ( logger.isWarnEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.warn("Error during delete on close, " + file.getFullName(), ex); + } + + // Return a node ref to update in the state table + return new Pair(nodeRef, isVersionable); + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) + logger.debug("Delete on close - access denied, " + file.getFullName()); + + // Convert to a filesystem access denied exception + + throw new AccessDeniedException("Delete on close " + file.getFullName()); } - - // Return a node ref to update in the state table - return new Pair(nodeRef, isVersionable); - } - catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) - { - // Debug - - if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) - logger.debug("Delete on close - access denied, " + file.getFullName()); - - // Convert to a filesystem access denied exception - - throw new AccessDeniedException("Delete on close " + file.getFullName()); } } } - } - - return null; - }}); - - if (result != null) - { - // Check if there is a quota manager enabled, release space back to the user quota - - if ( ctx.hasQuotaManager()) - ctx.getQuotaManager().releaseSpace(sess, tree, file.getFileId(), file.getFullName(), fileSize); - - // Set the file state to indicate a delete on close - - if (ctx.hasStateCache()) + + return null; + }}); + + if (result != null) { - if (result.getSecond()) + // Check if there is a quota manager enabled, release space back to the user quota + + if ( ctx.hasQuotaManager()) + ctx.getQuotaManager().releaseSpace(sess, tree, file.getFileId(), file.getFullName(), fileSize); + + // Set the file state to indicate a delete on close + + if (ctx.hasStateCache()) { - - // Get, or create, the file state - - FileState fState = ctx.getStateCache().findFileState(file.getFullName(), true); - - // Indicate that the file was deleted via a delete on close request - - fState.setFileStatus(DeleteOnClose); - - // Make sure the file state is cached for a short while, save the noderef details - - fState.setExpiryTime(System.currentTimeMillis() + FileState.RenameTimeout); - fState.setFilesystemObject(result.getFirst()); + if (result.getSecond()) + { + + // Get, or create, the file state + + FileState fState = ctx.getStateCache().findFileState(file.getFullName(), true); + + // Indicate that the file was deleted via a delete on close request + + fState.setFileStatus(DeleteOnClose); + + // Make sure the file state is cached for a short while, save the noderef details + + fState.setExpiryTime(System.currentTimeMillis() + FileState.RenameTimeout); + fState.setFilesystemObject(result.getFirst()); + } + else + { + + // Remove the file state + + ctx.getStateCache().removeFileState(file.getFullName()); + } } - else - { - - // Remove the file state - - ctx.getStateCache().removeFileState(file.getFullName()); + } + else if (file.hasDeleteOnClose() && (file instanceof PseudoNetworkFile || file instanceof MemoryNetworkFile) + && hasPseudoFileInterface(ctx)) + { + // Delete the pseudo file + + getPseudoFileInterface(ctx).deletePseudoFile(sess, tree, file.getFullName()); + + } + + // DEBUG + + if (logger.isDebugEnabled() && (ctx.hasDebug(AlfrescoContext.DBG_FILE) || ctx.hasDebug(AlfrescoContext.DBG_RENAME))) { + logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose()); + if ( file.hasDeleteOnClose() == false && file instanceof ContentNetworkFile) { + ContentNetworkFile cFile = (ContentNetworkFile) file; + logger.debug(" File " + file.getFullName() + ", version=" + nodeService.getProperty( cFile.getNodeRef(), ContentModel.PROP_VERSION_LABEL)); } } } - else if (file.hasDeleteOnClose() && (file instanceof PseudoNetworkFile || file instanceof MemoryNetworkFile) - && hasPseudoFileInterface(ctx)) + // Make sure we clean up before propagating exceptions + catch (IOException e) { - // Delete the pseudo file - - getPseudoFileInterface(ctx).deletePseudoFile(sess, tree, file.getFullName()); + try + { + doInWriteTransaction(sess, errorHandler); + } + catch (Throwable t) + { + logger.error(t.getMessage(), t); + } + throw e; } - - // DEBUG - - if (logger.isDebugEnabled() && (ctx.hasDebug(AlfrescoContext.DBG_FILE) || ctx.hasDebug(AlfrescoContext.DBG_RENAME))) { - logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose()); - if ( file.hasDeleteOnClose() == false && file instanceof ContentNetworkFile) { - ContentNetworkFile cFile = (ContentNetworkFile) file; - logger.debug(" File " + file.getFullName() + ", version=" + nodeService.getProperty( cFile.getNodeRef(), ContentModel.PROP_VERSION_LABEL)); + catch (RuntimeException e) + { + try + { + doInWriteTransaction(sess, errorHandler); } + catch (Throwable t) + { + logger.error(t.getMessage(), t); + } + throw e; + } + catch (Error e) + { + try + { + doInWriteTransaction(sess, errorHandler); + } + catch (Throwable t) + { + logger.error(t.getMessage(), t); + } + throw e; } } @@ -2921,13 +2974,40 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa boolean isFromVersionable = nodeService.hasAspect( nodeToMoveRef, ContentModel.ASPECT_VERSIONABLE); boolean typesCompatible = true; - if ( newExists == FileStatus.FileExists) { + // HACK ALF-3856: Version History lost when Versionable Content renamed via CIFS + // This code will move into the repo layer (or just above it) + // and this complexity removed from here. + // Attempt to detect normal renames. Hack alert! + String oldNameLower = oldName.toLowerCase(); + String newNameLower = newName.toLowerCase(); + boolean renameShuffle = false; + Pattern renameShufflePattern = ctx.getRenameShufflePattern(); + renameShuffle = renameShuffle || renameShufflePattern.matcher(oldNameLower).matches(); + renameShuffle = renameShuffle || renameShufflePattern.matcher(newNameLower).matches(); + if (logger.isDebugEnabled()) + { + logger.debug( + "Rename file: \n" + + " Old name: " + oldName + "\n" + + " New name: " + newName + "\n" + + " Pattern: " + renameShufflePattern.pattern() + "\n" + + " Is shuffle: " + renameShuffle + "\n" + + " Source folder: " + sourceFolderRef + "\n" + + " Target folder: " + targetFolderRef + "\n" + + " Node: " + nodeToMoveRef + "\n" + + " Aspects: " + nodeService.getAspects(nodeToMoveRef)); + + } + + if ( newExists == FileStatus.FileExists) + { // Use the existing file as the target node targetNodeRef = getNodeForPath( tree, newName); } - else { + else if (renameShuffle) + { // Check if the target has a renamed or delete-on-close state @@ -3043,10 +3123,12 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa } } - // If the original or target nodes are not versionable and types are compatible then just use a standard rename of the - // node - if ( isFromVersionable == false && typesCompatible && - ( targetNodeRef == null || nodeService.hasAspect( targetNodeRef, ContentModel.ASPECT_VERSIONABLE) == false)) { + // If the original or target nodes are not versionable and types are compatible then just use a standard rename of the node + if ( !renameShuffle || + ( !isFromVersionable && + typesCompatible && + ( targetNodeRef == null || nodeService.hasAspect( targetNodeRef, ContentModel.ASPECT_VERSIONABLE) == false))) + { // Rename the file/folder diff --git a/source/java/org/alfresco/filesys/repo/NodeMonitor.java b/source/java/org/alfresco/filesys/repo/NodeMonitor.java index 517161e231..352c3da500 100644 --- a/source/java/org/alfresco/filesys/repo/NodeMonitor.java +++ b/source/java/org/alfresco/filesys/repo/NodeMonitor.java @@ -32,9 +32,11 @@ import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileFolderServiceType; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -386,20 +388,8 @@ public class NodeMonitor extends TransactionListenerAdapter if ( fType != FileFolderServiceType.INVALID) { - // Get the full path to the file/folder node - - Path nodePath = m_nodeService.getPath( nodeRef); - String fName = (String) m_nodeService.getProperty( nodeRef, ContentModel.PROP_NAME); - - // Build the share relative path to the node - - StringBuilder pathStr = new StringBuilder(); - pathStr.append( nodePath.toDisplayPath(m_nodeService, m_permissionService)); - if ( pathStr.length() == 0 || (pathStr.charAt(pathStr.length() - 1) != '/' && pathStr.charAt(pathStr.length() - 1) != '\\')) - pathStr.append("\\"); - pathStr.append( fName); - - String relPath = pathStr.toString(); + StringBuilder pathStr = calculateDisplayPath(nodeRef); + String relPath = (null != pathStr) ? (pathStr.toString()):(""); // Create an event to process the node deletion @@ -422,6 +412,29 @@ public class NodeMonitor extends TransactionListenerAdapter } } + private StringBuilder calculateDisplayPath(final NodeRef nodeRef) + { + return AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public StringBuilder doWork() throws Exception + { + // Get the full path to the file/folder node + Path nodePath = m_nodeService.getPath(nodeRef); + String fName = (String) m_nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + + // Build the share relative path to the node + StringBuilder result = new StringBuilder(); + result.append(nodePath.toDisplayPath(m_nodeService, m_permissionService)); + if ((0 == result.length()) || ('/' != (result.charAt(result.length() - 1)) && ('\\' != result.charAt(result.length() - 1)))) + { + result.append("\\"); + } + return result.append(fName); + } + }, AuthenticationUtil.SYSTEM_USER_NAME); + } + /** * The relative path of a renamed/moved node * diff --git a/source/java/org/alfresco/repo/action/ActionDefinitionImpl.java b/source/java/org/alfresco/repo/action/ActionDefinitionImpl.java index bcf4408187..a6092ac81d 100644 --- a/source/java/org/alfresco/repo/action/ActionDefinitionImpl.java +++ b/source/java/org/alfresco/repo/action/ActionDefinitionImpl.java @@ -28,21 +28,13 @@ import org.alfresco.service.namespace.QName; * * @author Roy Wetherall */ -public class ActionDefinitionImpl extends ParameterizedItemDefinitionImpl - implements ActionDefinition +public class ActionDefinitionImpl extends ParameterizedItemDefinitionImpl implements ActionDefinition { - /** - * Serial version UID - */ private static final long serialVersionUID = 4048797883396863026L; - /** - * The rule action executor - */ private String ruleActionExecutor; - - /** List of applicable types */ private List applicableTypes; + private boolean trackStatus; /** * Constructor @@ -52,6 +44,7 @@ public class ActionDefinitionImpl extends ParameterizedItemDefinitionImpl public ActionDefinitionImpl(String name) { super(name); + this.trackStatus = false; } /** @@ -93,4 +86,27 @@ public class ActionDefinitionImpl extends ParameterizedItemDefinitionImpl { this.applicableTypes = applicableTypes; } + + @Override + public boolean getTrackStatus() + { + return trackStatus; + } + + /** + * Set whether the basic action definition requires status tracking. + * This can be overridden on each action instance but if not, it falls back + * to this definition. + *

+ * Setting this to true introduces performance problems for concurrently-executing + * rules on V3.4: ALF-7341. + * + * @param trackStatus true to track execution status otherwise false + * + * @since 3.4.1 + */ + public void setTrackStatus(boolean trackStatus) + { + this.trackStatus = trackStatus; + } } diff --git a/source/java/org/alfresco/repo/action/ActionImpl.java b/source/java/org/alfresco/repo/action/ActionImpl.java index b16677b27a..3508b8cafc 100644 --- a/source/java/org/alfresco/repo/action/ActionImpl.java +++ b/source/java/org/alfresco/repo/action/ActionImpl.java @@ -38,72 +38,21 @@ import org.alfresco.service.cmr.repository.NodeRef; */ public class ActionImpl extends ParameterizedItemImpl implements Action { - /** - * Serial version UID - */ private static final long serialVersionUID = 3258135760426186548L; - /** The node reference for the action */ private NodeRef nodeRef; - - /** - * The title - */ private String title; - - /** - * The description - */ private String description; - - /** - * Inidcates whether the action should be executed asynchronously or not - */ + private Boolean trackStatus = null; private boolean executeAsynchronously = false; - - /** - * The compensating action - */ private Action compensatingAction; - - /** - * The created date - */ private Date createdDate; - - /** - * The creator - */ private String creator; - - /** - * The modified date - */ private Date modifiedDate; - - /** - * The modifier - */ private String modifier; - - /** - * Rule action definition name - */ private String actionDefinitionName; - - /** - * The run as user name - */ private String runAsUserName; - - /** - * The chain of actions that have lead to this action - */ private Set actionChain; - - /** - * Action conditions - */ private List actionConditions = new ArrayList(); /** @@ -117,14 +66,12 @@ public class ActionImpl extends ParameterizedItemImpl implements Action private int executionInstance = -1; /** - * When the action started executing, - * or null if it hasn't yet. + * When the action started executing, or null if it hasn't yet. */ private Date executionStartDate; /** - * When the action finished executing, - * or null if it hasn't yet. + * When the action finished executing, or null if it hasn't yet. */ private Date executionEndDate; @@ -175,6 +122,7 @@ public class ActionImpl extends ParameterizedItemImpl implements Action this.createdDate = action.getCreatedDate(); this.creator = action.getCreator(); this.description = action.getDescription(); + this.trackStatus = action.getTrackStatus(); this.executeAsynchronously = action.getExecuteAsychronously(); this.modifiedDate = action.getModifiedDate(); this.modifier = action.getModifier(); @@ -206,73 +154,67 @@ public class ActionImpl extends ParameterizedItemImpl implements Action return sb.toString(); } - /** - * @see org.alfresco.service.cmr.action.Action#getTitle() - */ + @Override public String getTitle() { return this.title; } - /** - * @see org.alfresco.service.cmr.action.Action#setTitle(java.lang.String) - */ + @Override public void setTitle(String title) { this.title = title; } - /** - * @see org.alfresco.service.cmr.action.Action#getDescription() - */ + @Override public String getDescription() { return this.description; } - /** - * @see org.alfresco.service.cmr.action.Action#setDescription(java.lang.String) - */ + @Override public void setDescription(String description) { this.description = description; } - /** - * @see org.alfresco.service.cmr.action.Action#getExecuteAsychronously() - */ + @Override + public Boolean getTrackStatus() + { + return trackStatus; + } + + @Override + public void setTrackStatus(Boolean trackStatus) + { + this.trackStatus = trackStatus; + } + + @Override public boolean getExecuteAsychronously() { return this.executeAsynchronously; } - /** - * @see org.alfresco.service.cmr.action.Action#setExecuteAsynchronously(boolean) - */ + @Override public void setExecuteAsynchronously(boolean executeAsynchronously) { this.executeAsynchronously = executeAsynchronously; } - /** - * @see org.alfresco.service.cmr.action.Action#getCompensatingAction() - */ + @Override public Action getCompensatingAction() { return this.compensatingAction; } - /** - * @see org.alfresco.service.cmr.action.Action#setCompensatingAction(org.alfresco.service.cmr.action.Action) - */ + @Override public void setCompensatingAction(Action action) { this.compensatingAction = action; } - /** - * @see org.alfresco.service.cmr.action.Action#getCreatedDate() - */ + @Override public Date getCreatedDate() { return this.createdDate; @@ -288,9 +230,7 @@ public class ActionImpl extends ParameterizedItemImpl implements Action this.createdDate = createdDate; } - /** - * @see org.alfresco.service.cmr.action.Action#getCreator() - */ + @Override public String getCreator() { return this.creator; @@ -298,17 +238,13 @@ public class ActionImpl extends ParameterizedItemImpl implements Action /** * Set the creator - * - * @param creator the creator */ public void setCreator(String creator) { this.creator = creator; } - /** - * @see org.alfresco.service.cmr.action.Action#getModifiedDate() - */ + @Override public Date getModifiedDate() { return this.modifiedDate; @@ -316,17 +252,13 @@ public class ActionImpl extends ParameterizedItemImpl implements Action /** * Set the modified date - * - * @param modifiedDate the modified date */ public void setModifiedDate(Date modifiedDate) { this.modifiedDate = modifiedDate; } - /** - * @see org.alfresco.service.cmr.action.Action#getModifier() - */ + @Override public String getModifier() { return this.modifier; @@ -334,91 +266,67 @@ public class ActionImpl extends ParameterizedItemImpl implements Action /** * Set the modifier - * - * @param modifier the modifier */ public void setModifier(String modifier) { this.modifier = modifier; } - /** - * @see org.alfresco.service.cmr.action.Action#getActionDefinitionName() - */ + @Override public String getActionDefinitionName() { return this.actionDefinitionName; } - /** - * @see org.alfresco.service.cmr.action.Action#hasActionConditions() - */ + @Override public boolean hasActionConditions() { return (this.actionConditions.isEmpty() == false); } - /** - * @see org.alfresco.service.cmr.action.Action#indexOfActionCondition(org.alfresco.service.cmr.action.ActionCondition) - */ + @Override public int indexOfActionCondition(ActionCondition actionCondition) { return this.actionConditions.indexOf(actionCondition); } - /** - * @see org.alfresco.service.cmr.action.Action#getActionConditions() - */ + @Override public List getActionConditions() { return this.actionConditions; } - /** - * @see org.alfresco.service.cmr.action.Action#getActionCondition(int) - */ + @Override public ActionCondition getActionCondition(int index) { return this.actionConditions.get(index); } - /** - * @see org.alfresco.service.cmr.action.Action#addActionCondition(org.alfresco.service.cmr.action.ActionCondition) - */ + @Override public void addActionCondition(ActionCondition actionCondition) { this.actionConditions.add(actionCondition); } - /** - * @see org.alfresco.service.cmr.action.Action#addActionCondition(int, - * org.alfresco.service.cmr.action.ActionCondition) - */ + @Override public void addActionCondition(int index, ActionCondition actionCondition) { this.actionConditions.add(index, actionCondition); } - /** - * @see org.alfresco.service.cmr.action.Action#setActionCondition(int, - * org.alfresco.service.cmr.action.ActionCondition) - */ + @Override public void setActionCondition(int index, ActionCondition actionCondition) { this.actionConditions.set(index, actionCondition); } - /** - * @see org.alfresco.service.cmr.action.Action#removeActionCondition(org.alfresco.service.cmr.action.ActionCondition) - */ + @Override public void removeActionCondition(ActionCondition actionCondition) { this.actionConditions.remove(actionCondition); } - /** - * @see org.alfresco.service.cmr.action.Action#removeAllActionConditions() - */ + @Override public void removeAllActionConditions() { this.actionConditions.clear(); @@ -454,9 +362,7 @@ public class ActionImpl extends ParameterizedItemImpl implements Action this.runAsUserName = runAsUserName; } - /** - * @see org.alfresco.service.cmr.action.Action#getNodeRef() - */ + @Override public NodeRef getNodeRef() { return this.nodeRef; @@ -472,6 +378,7 @@ public class ActionImpl extends ParameterizedItemImpl implements Action this.nodeRef = nodeRef; } + @Override public void addParameterValues(Map values) { getParameterValues().putAll(values); @@ -491,8 +398,7 @@ public class ActionImpl extends ParameterizedItemImpl implements Action } /** - * Called by the ActionService when the action - * begins running. + * Called by the ActionService when the action begins running. */ public void setExecutionInstance(int instance) { executionInstance = instance; diff --git a/source/java/org/alfresco/repo/action/ActionModel.java b/source/java/org/alfresco/repo/action/ActionModel.java index 530855213f..1ad9ad4ffa 100644 --- a/source/java/org/alfresco/repo/action/ActionModel.java +++ b/source/java/org/alfresco/repo/action/ActionModel.java @@ -29,6 +29,7 @@ public interface ActionModel static final QName PROP_DEFINITION_NAME = QName.createQName(ACTION_MODEL_URI, "definitionName"); static final QName PROP_ACTION_TITLE = QName.createQName(ACTION_MODEL_URI, "actionTitle"); static final QName PROP_ACTION_DESCRIPTION = QName.createQName(ACTION_MODEL_URI, "actionDescription"); + static final QName PROP_TRACK_STATUS = QName.createQName(ACTION_MODEL_URI, "trackStatus"); static final QName PROP_EXECUTE_ASYNCHRONOUSLY = QName.createQName(ACTION_MODEL_URI, "executeAsynchronously"); static final QName PROP_EXECUTION_START_DATE = QName.createQName(ACTION_MODEL_URI, "executionStartDate"); static final QName PROP_EXECUTION_END_DATE = QName.createQName(ACTION_MODEL_URI, "executionEndDate"); diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java index 26d5676ef3..09817aee1d 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java @@ -61,7 +61,6 @@ import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; 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.GUID; import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; @@ -468,9 +467,8 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A { if (logger.isDebugEnabled()) { - logger - .debug("Evaluating composite condition " - + simplecondition.getActionConditionDefinitionName()); + logger.debug( + "Evaluating composite condition " + simplecondition.getActionConditionDefinitionName()); } if (compositeCondition.isORCondition()) @@ -571,8 +569,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A } /** - * @param compensatingAction - * @param actionedUponNodeRef + * Queue a compensating action for execution against a specific node */ private void queueAction(Action compensatingAction, NodeRef actionedUponNodeRef) { @@ -583,6 +580,9 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A queue.executeAction(this, compensatingAction, actionedUponNodeRef, false, null); } + /** + * Get the queue to use for asynchronous execution of the given action. + */ private AsynchronousActionExecutionQueue getQueue(Action action) { ActionExecuter executer = (ActionExecuter) this.applicationContext.getBean(action.getActionDefinitionName()); @@ -605,12 +605,34 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A return queue; } - + /** - * @see org.alfresco.repo.action.RuntimeActionService#executeActionImpl(org.alfresco.service.cmr.action.Action, - * org.alfresco.service.cmr.repository.NodeRef, boolean, - * org.alfresco.service.cmr.repository.NodeRef) + * Get whether the action should be tracked by the {@link ActionTrackingService} or not. + * + * @param action the action, which may or may not have any say about the status tracking + * @return true if the status must be tracked, otherwise false */ + private boolean getTrackStatus(Action action) + { + Boolean trackStatusManual = action.getTrackStatus(); + if (trackStatusManual != null) + { + return trackStatusManual.booleanValue(); + } + // The flag has not be changed for the specific action, so use the value from the + // action definition. + ActionDefinition actionDef = getActionDefinition(action.getActionDefinitionName()); + if (actionDef == null) + { + return false; // default to 'false' if the definition has disappeared + } + else + { + return actionDef.getTrackStatus(); + } + } + + @Override public void executeActionImpl(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, boolean executedAsynchronously, Set actionChain) { @@ -668,14 +690,20 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A // Check and execute now if (checkConditions == false || evaluateAction(action, actionedUponNodeRef) == true) { - // Mark the action as starting - actionTrackingService.recordActionExecuting(action); + if (getTrackStatus(action)) + { + // Mark the action as starting + actionTrackingService.recordActionExecuting(action); + } // Execute the action directActionExecution(action, actionedUponNodeRef); - // Mark it as having worked - actionTrackingService.recordActionComplete(action); + if (getTrackStatus(action)) + { + // Mark it as having worked + actionTrackingService.recordActionComplete(action); + } } } finally @@ -713,8 +741,11 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A } } - // Have the failure logged on the action - actionTrackingService.recordActionFailure(action, exception); + if (getTrackStatus(action)) + { + // Have the failure logged on the action + actionTrackingService.recordActionFailure(action, exception); + } // Rethrow the exception if (exception instanceof RuntimeException) @@ -901,6 +932,10 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A Map props = this.nodeService.getProperties(actionNodeRef); props.put(ActionModel.PROP_ACTION_TITLE, action.getTitle()); props.put(ActionModel.PROP_ACTION_DESCRIPTION, action.getDescription()); + if (action.getTrackStatus() != null) + { + props.put(ActionModel.PROP_TRACK_STATUS, action.getTrackStatus()); + } props.put(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY, action.getExecuteAsychronously()); props.put(ActionModel.PROP_EXECUTION_START_DATE, action.getExecutionStartDate()); @@ -1300,13 +1335,12 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A action.setTitle((String) props.get(ActionModel.PROP_ACTION_TITLE)); action.setDescription((String) props.get(ActionModel.PROP_ACTION_DESCRIPTION)); - boolean value = false; - Boolean executeAsynchronously = (Boolean) props.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); - if (executeAsynchronously != null) - { - value = executeAsynchronously.booleanValue(); - } - action.setExecuteAsynchronously(value); + Boolean trackStatusObj = (Boolean) props.get(ActionModel.PROP_TRACK_STATUS); + action.setTrackStatus(trackStatusObj); // Allowed to be null + + Boolean executeAsynchObj = (Boolean) props.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); + boolean executeAsynch = executeAsynchObj == null ? false : executeAsynchObj.booleanValue(); + action.setExecuteAsynchronously(executeAsynch); ((ActionImpl) action).setCreator((String) props.get(ContentModel.PROP_CREATOR)); ((ActionImpl) action).setCreatedDate((Date) props.get(ContentModel.PROP_CREATED)); diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java index 0d07fbc29f..9ce27872da 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -1285,16 +1285,20 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest return failingAction; } - protected Action createWorkingSleepAction() throws Exception { + protected Action createWorkingSleepAction() throws Exception + { return createWorkingSleepAction(null); } - protected Action createWorkingSleepAction(String id) throws Exception { + protected Action createWorkingSleepAction(String id) throws Exception + { return createWorkingSleepAction(id, this.actionService); } - protected static Action createWorkingSleepAction(String id, ActionService actionService) throws Exception { - Action workingAction = new CancellableSleepAction( - actionService.createAction(SleepActionExecuter.NAME)); - if(id != null) { + protected static Action createWorkingSleepAction(String id, ActionService actionService) throws Exception + { + Action workingAction = new CancellableSleepAction(actionService.createAction(SleepActionExecuter.NAME)); + workingAction.setTrackStatus(Boolean.TRUE); + if(id != null) + { Field idF = ParameterizedItemImpl.class.getDeclaredField("id"); idF.setAccessible(true); idF.set(workingAction, id); @@ -1337,24 +1341,20 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest private ActionTrackingService actionTrackingService; /** - * Loads this executor into the ApplicationContext, if it - * isn't already there + * Loads this executor into the ApplicationContext, if it isn't already there */ - public static void registerIfNeeded(ConfigurableApplicationContext ctx) - { - if(!ctx.containsBean(SleepActionExecuter.NAME)) - { - // Create, and do dependencies - SleepActionExecuter executor = new SleepActionExecuter(); - executor.actionTrackingService = (ActionTrackingService) - ctx.getBean("actionTrackingService"); - // Register - ctx.getBeanFactory().registerSingleton( - SleepActionExecuter.NAME, - executor - ); - } - } + public static void registerIfNeeded(ConfigurableApplicationContext ctx) + { + if (!ctx.containsBean(SleepActionExecuter.NAME)) + { + // Create, and do dependencies + SleepActionExecuter executor = new SleepActionExecuter(); + executor.setTrackStatus(true); + executor.actionTrackingService = (ActionTrackingService) ctx.getBean("actionTrackingService"); + // Register + ctx.getBeanFactory().registerSingleton(SleepActionExecuter.NAME, executor); + } + } public Thread getExecutingThread() { @@ -1415,9 +1415,11 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest } protected static class CancellableSleepAction extends ActionImpl implements CancellableAction { - public CancellableSleepAction(Action action) { - super(action); - } + public CancellableSleepAction(Action action) + { + super(action); + this.setTrackStatus(Boolean.TRUE); + } } public static void assertBefore(Date before, Date after) diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java index cfcb14a471..444162689b 100644 --- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java @@ -1225,22 +1225,30 @@ public class ActionTrackingServiceImplTest extends TestCase } } - - private Action createFailingMoveAction() { - Action failingAction = this.actionService.createAction(MoveActionExecuter.NAME); - failingAction.setParameterValue(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); - failingAction.setParameterValue(MoveActionExecuter.PARAM_ASSOC_QNAME, ContentModel.ASSOC_CHILDREN); - // Create a bad node ref - NodeRef badNodeRef = new NodeRef(this.storeRef, "123123"); - failingAction.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef); - - return failingAction; + private Action createFailingMoveAction() + { + Action failingAction = this.actionService.createAction(MoveActionExecuter.NAME); + failingAction.setTrackStatus(Boolean.TRUE); + failingAction.setParameterValue( + MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, + ContentModel.ASSOC_CHILDREN); + failingAction.setParameterValue( + MoveActionExecuter.PARAM_ASSOC_QNAME, + ContentModel.ASSOC_CHILDREN); + // Create a bad node ref + NodeRef badNodeRef = new NodeRef(this.storeRef, "123123"); + failingAction.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef); + + return failingAction; } - - private Action createFailingSleepAction(String id) throws Exception { - return ActionServiceImplTest.createFailingSleepAction(id, actionService); + + private Action createFailingSleepAction(String id) throws Exception + { + return ActionServiceImplTest.createFailingSleepAction(id, actionService); } - private Action createWorkingSleepAction(String id) throws Exception { - return ActionServiceImplTest.createWorkingSleepAction(id, actionService); + + private Action createWorkingSleepAction(String id) throws Exception + { + return ActionServiceImplTest.createWorkingSleepAction(id, actionService); } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java index 630c501838..87d5297b54 100644 --- a/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java +++ b/source/java/org/alfresco/repo/action/AsynchronousActionExecutionQueueImpl.java @@ -18,13 +18,11 @@ */ package org.alfresco.repo.action; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.Vector; -import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadPoolExecutor; @@ -37,14 +35,12 @@ import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionServiceException; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -65,8 +61,6 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE private Map actionFilters = new ConcurrentHashMap(); - private NodeService nodeService; - /** * We keep a record of ongoing asynchronous actions (this includes those being executed and * those that are in the queue). @@ -132,11 +126,6 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE this.policyComponent = policyComponent; } - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - private void invokeOnAsyncActionExecutePolicy(Action action, NodeRef actionedUponNodeRef) { // Execute the policy, passing it all details, firing as a general action case @@ -145,35 +134,6 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE policy.onAsyncActionExecute(action, actionedUponNodeRef); } - /** - * Get all aspect and node type qualified names - * - * @param nodeRef - * the node we are interested in - * @return Returns a set of qualified names containing the node type and all - * the node aspects, or null if the node no longer exists - */ - private Set getTypeAndAspectQNames(NodeRef nodeRef) - { - Set qnames = null; - try - { - Set aspectQNames = this.nodeService.getAspects(nodeRef); - - QName typeQName = this.nodeService.getType(nodeRef); - - qnames = new HashSet(aspectQNames.size() + 1); - qnames.addAll(aspectQNames); - qnames.add(typeQName); - } - catch (InvalidNodeRefException e) - { - qnames = Collections.emptySet(); - } - // done - return qnames; - } - /** * This method registers an action filter, which can be used to prevent unwanted or unnecessary * asynchronous actions from being scheduled for execution. diff --git a/source/java/org/alfresco/repo/action/RuntimeActionService.java b/source/java/org/alfresco/repo/action/RuntimeActionService.java index 82bee9984f..c8837a505f 100644 --- a/source/java/org/alfresco/repo/action/RuntimeActionService.java +++ b/source/java/org/alfresco/repo/action/RuntimeActionService.java @@ -35,38 +35,38 @@ import org.alfresco.service.namespace.QName; */ public interface RuntimeActionService { - /** - * Post commit method - */ - void postCommit(); - + /** + * Post commit method + */ + void postCommit(); + /** * Register an action condition evaluator * * @param actionConditionEvaluator action condition evaluator */ - void registerActionConditionEvaluator(ActionConditionEvaluator actionConditionEvaluator); - + void registerActionConditionEvaluator(ActionConditionEvaluator actionConditionEvaluator); + /** * Register an action executer * * @param actionExecuter action executer */ - void registerActionExecuter(ActionExecuter actionExecuter); - - /** - * Register parameter constraint - * - * @param parameterConstraint parameter constraint - */ + void registerActionExecuter(ActionExecuter actionExecuter); + + /** + * Register parameter constraint + * + * @param parameterConstraint parameter constraint + */ void registerParameterConstraint(ParameterConstraint parameterConstraint); - - /** - * Create a new action based on an action node reference - * - * @param actionNodeRef action node reference - * @return Action action object - */ + + /** + * Create a new action based on an action node reference + * + * @param actionNodeRef action node reference + * @return Action action object + */ Action createAction(NodeRef actionNodeRef); /** @@ -79,33 +79,30 @@ public interface RuntimeActionService * @return NodeRef created node reference */ NodeRef createActionNodeRef(Action action, NodeRef parentNodeRef, QName assocTypeName, QName assocName); - - /** - * Save action, used internally to store the details of an action on the aciton node. - * - * @param actionNodeRef the action node reference - * @param action the action - */ - void saveActionImpl(NodeRef actionNodeRef, Action action); - - /** - * - * @param action - * @param actionedUponNodeRef - * @param checkConditions - */ - public void executeActionImpl( - Action action, - NodeRef actionedUponNodeRef, - boolean checkConditions, - boolean executedAsynchronously, + + /** + * Save action, used internally to store the details of an action on the aciton node. + * + * @param actionNodeRef the action node reference + * @param action the action + */ + void saveActionImpl(NodeRef actionNodeRef, Action action); + + /** + * Perform low-level action execution + */ + public void executeActionImpl( + Action action, + NodeRef actionedUponNodeRef, + boolean checkConditions, + boolean executedAsynchronously, Set actionChain); - + /** * Execute an action directly * * @param action the action * @param actionedUponNodeRef the actioned upon node reference */ - public void directActionExecution(Action action, NodeRef actionedUponNodeRef); + public void directActionExecution(Action action, NodeRef actionedUponNodeRef); } diff --git a/source/java/org/alfresco/repo/action/actionModel.xml b/source/java/org/alfresco/repo/action/actionModel.xml index 1c31a31dcd..b17527367d 100644 --- a/source/java/org/alfresco/repo/action/actionModel.xml +++ b/source/java/org/alfresco/repo/action/actionModel.xml @@ -66,6 +66,10 @@ d:text false + + d:boolean + false + d:boolean true diff --git a/source/java/org/alfresco/repo/action/constraint/FolderContentsParameterConstraint.java b/source/java/org/alfresco/repo/action/constraint/FolderContentsParameterConstraint.java index 2cd824da07..e873458562 100644 --- a/source/java/org/alfresco/repo/action/constraint/FolderContentsParameterConstraint.java +++ b/source/java/org/alfresco/repo/action/constraint/FolderContentsParameterConstraint.java @@ -19,6 +19,8 @@ package org.alfresco.repo.action.constraint; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -50,6 +52,8 @@ public class FolderContentsParameterConstraint extends BaseParameterConstraint private String searchPath; + private List nodeInclusionFilter = Collections.emptyList(); + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; @@ -70,6 +74,30 @@ public class FolderContentsParameterConstraint extends BaseParameterConstraint this.dictionaryService = dictionaryService; } + /** + * This optional property defines a list of file extensions which should be included in the result set from + * this class. By implication, all other file extensions will be excluded. (The dot should not be specified + * i.e. "txt" not ".txt"). + * If present, the cm:name of each candidate node will be checked against the values in this list and + * only those nodes whose cm:name ends with one of these file extensions will be included. + *

+ * If the property is not set then no inclusion filter is specified and all file extensions will + * be included. + * + * @param nodeInclusionFilter A list of file extensions + * @since 3.5 + */ + public void setNodeInclusionFilter(List nodeInclusionFilter) + { + // We'll convert the extensions from spring to dot+extension + this.nodeInclusionFilter = new ArrayList(nodeInclusionFilter.size()); + for (String extension : nodeInclusionFilter) + { + StringBuilder dotExt = new StringBuilder().append(".").append(extension); + this.nodeInclusionFilter.add(dotExt.toString()); + } + } + /** * @see org.alfresco.service.cmr.action.ParameterConstraint#getAllowableValues() */ @@ -103,15 +131,18 @@ public class FolderContentsParameterConstraint extends BaseParameterConstraint QName className = nodeService.getType(nodeRef); if (dictionaryService.isSubClass(className, ContentModel.TYPE_CONTENT) == true) { - String title = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); - if (title != null && title.length() > 0) + if(isCmNameAcceptable(nodeRef)) { - result.put(nodeRef.toString(), title); + String title = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); + if (title != null && title.length() > 0) + { + result.put(nodeRef.toString(), title); + } + else + { + result.put(nodeRef.toString(), (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME)); + } } - else - { - result.put(nodeRef.toString(), (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME)); - } } else if (dictionaryService.isSubClass(className, ContentModel.TYPE_FOLDER) == true) { @@ -119,4 +150,39 @@ public class FolderContentsParameterConstraint extends BaseParameterConstraint } } } + + /** + * Folder contents as returned by this class can be filtered based on the cm:name of the + * contained content nodes. If no file extensions are included, then all content NodeRefs + * will be included in the result set. + * If however, there are any file extensions specified, then the cm:name must match one of + * those file extensions to be included in the result set. + * + * @param nodeRef the node whose cm:name is to be checked. + * @return true if cm:name is acceptable, else false. + */ + private boolean isCmNameAcceptable(NodeRef nodeRef) + { + // Implementation node: this is very similar to a MIME type check. However, it is + // more forgiving in that content with the wrong MIME type will be correctly included. + // e.g. it is fairly common for .js files to be saved with a MIME type of text/plain. + boolean result = true; + + if (!nodeInclusionFilter.isEmpty()) + { + result = false; + String cmName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + + for (String extension : nodeInclusionFilter) + { + if (cmName.endsWith(extension)) + { + result = true; + break; + } + } + } + + return result; + } } diff --git a/source/java/org/alfresco/repo/action/executer/ActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ActionExecuter.java index afdff76d37..cf0747a0ab 100644 --- a/source/java/org/alfresco/repo/action/executer/ActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ActionExecuter.java @@ -48,6 +48,18 @@ public interface ActionExecuter */ boolean getIgnoreLock(); + /** + * Get whether the basic action definition supports action tracking + * or not. This can be overridden for each {@link Action#getTrackStatus() action} + * but if not, this value is used. Defaults to false. + * + * @return true to track action execution status or false (default) + * to do no action tracking + * + * @since 3.4.1 + */ + boolean getTrackStatus(); + /** * Get the action definition for the action * @@ -61,7 +73,5 @@ public interface ActionExecuter * @param action the action * @param actionedUponNodeRef the actioned upon node reference */ - void execute(Action action, - NodeRef actionedUponNodeRef); - + void execute(Action action, NodeRef actionedUponNodeRef); } diff --git a/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java b/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java index 4f82354fcf..5b2dab026f 100644 --- a/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java +++ b/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java @@ -22,13 +22,13 @@ import java.util.ArrayList; import java.util.List; import org.alfresco.repo.action.ActionDefinitionImpl; -import org.alfresco.repo.action.ActionServiceImpl; import org.alfresco.repo.action.ParameterizedItemAbstractBase; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionDefinition; import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -40,14 +40,17 @@ import org.apache.commons.logging.LogFactory; */ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstractBase implements ActionExecuter { - /** Action definition */ - protected ActionDefinition actionDefinition; - - /** Lock service */ - private LockService lockService; - - /** Indicated whether the action is public or internal */ - protected boolean publicAction = true; + private static Log logger = LogFactory.getLog(ActionExecuterAbstractBase.class); + + protected ActionDefinition actionDefinition; + private LockService lockService; + private NodeService baseNodeService; + + /** Indicate if the action status should be tracked or not (default false) */ + private boolean trackStatus = false; + + /** Indicated whether the action is public or internal (default true) */ + protected boolean publicAction = true; /** List of types and aspects for which this action is applicable */ protected List applicableTypes = new ArrayList(); @@ -58,40 +61,64 @@ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstra /** Indicates whether the action should be ignored if the actioned upon node is locked */ private boolean ignoreLock = true; - /** Logger */ - private static Log logger = LogFactory.getLog(ActionExecuterAbstractBase.class); - - /** - * Init method - */ - public void init() - { - if (this.publicAction == true) - { - this.runtimeActionService.registerActionExecuter(this); - } - } - - /** - * Sets the lock service - * - * @param lockService lock service - */ - public void setLockService(LockService lockService) - { - this.lockService = lockService; - } - - /** - * Set whether the action is public or not. - * - * @param publicAction true if the action is public, false otherwise - */ - public void setPublicAction(boolean publicAction) - { - this.publicAction = publicAction; - } + /** + * Init method + */ + public void init() + { + if (this.publicAction == true) + { + this.runtimeActionService.registerActionExecuter(this); + } + } + public void setLockService(LockService lockService) + { + this.lockService = lockService; + } + + public void setBaseNodeService(NodeService nodeService) + { + this.baseNodeService = nodeService; + } + + /** + * Set whether the action is public or not. + * + * @param publicAction true if the action is public, false otherwise + */ + public void setPublicAction(boolean publicAction) + { + this.publicAction = publicAction; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean getTrackStatus() + { + return trackStatus; + } + + /** + * Set whether the basic action definition requires status tracking. + * This can be overridden on each action instance but if not, it falls back + * to this definition. + *

+ * Setting this to true introduces performance problems for concurrently-executing + * rules on V3.4: ALF-7341. + * It should only be used for long, seldom-run actions. + * + * @param trackStatus true to track execution status otherwise false + * + * @since 3.4.1 + */ + public void setTrackStatus(boolean trackStatus) + { + this.trackStatus = trackStatus; + } + /** * Set the list of types for which this action is applicable * @@ -110,52 +137,50 @@ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstra */ public boolean getIgnoreLock() { - return this.ignoreLock; + return this.ignoreLock; } /** * Set the ignore lock value. - * @param ignoreLock true if lock should be ignored on actioned upon node, false otherwise + * @param ignoreLock true if lock should be ignored on actioned upon node, false otherwise */ public void setIgnoreLock(boolean ignoreLock) { - this.ignoreLock = ignoreLock; + this.ignoreLock = ignoreLock; } - - /** - * Get rule action definition - * - * @return the action definition object - */ - public ActionDefinition getActionDefinition() - { - if (this.actionDefinition == null) - { - this.actionDefinition = createActionDefinition(this.name); - ((ActionDefinitionImpl)this.actionDefinition).setTitleKey(getTitleKey()); - ((ActionDefinitionImpl)this.actionDefinition).setDescriptionKey(getDescriptionKey()); - ((ActionDefinitionImpl)this.actionDefinition).setAdhocPropertiesAllowed(getAdhocPropertiesAllowed()); - ((ActionDefinitionImpl)this.actionDefinition).setRuleActionExecutor(this.name); - ((ActionDefinitionImpl)this.actionDefinition).setParameterDefinitions(getParameterDefintions()); + + /** + * Get rule action definition + * + * @return the action definition object + */ + public ActionDefinition getActionDefinition() + { + if (this.actionDefinition == null) + { + this.actionDefinition = createActionDefinition(this.name); + ((ActionDefinitionImpl)this.actionDefinition).setTitleKey(getTitleKey()); + ((ActionDefinitionImpl)this.actionDefinition).setDescriptionKey(getDescriptionKey()); + ((ActionDefinitionImpl)this.actionDefinition).setTrackStatus(getTrackStatus()); + ((ActionDefinitionImpl)this.actionDefinition).setAdhocPropertiesAllowed(getAdhocPropertiesAllowed()); + ((ActionDefinitionImpl)this.actionDefinition).setRuleActionExecutor(this.name); + ((ActionDefinitionImpl)this.actionDefinition).setParameterDefinitions(getParameterDefintions()); ((ActionDefinitionImpl)this.actionDefinition).setApplicableTypes(this.applicableTypes); - } - return this.actionDefinition; - } - - /** - * This method returns an instance of an ActionDefinition implementation class. By default - * this will be an {@link ActionDefinitionImpl}, but this could be overridden. - * - * @param name - * @return - */ - protected ActionDefinition createActionDefinition(String name) - { - return new ActionDefinitionImpl(name); - } - - /** - * @see org.alfresco.repo.action.executer.ActionExecuter#execute(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef) + } + return this.actionDefinition; + } + + /** + * This method returns an instance of an ActionDefinition implementation class. By default + * this will be an {@link ActionDefinitionImpl}, but this could be overridden. + */ + protected ActionDefinition createActionDefinition(String name) + { + return new ActionDefinitionImpl(name); + } + + /** + * {@inheritDoc} */ public void execute(Action action, NodeRef actionedUponNodeRef) { @@ -165,52 +190,57 @@ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstra // Only execute the action if this action is read only or the actioned upon node reference doesn't // have a lock applied for this user. if (ignoreLock == true || - hasLock(actionedUponNodeRef) == false) + hasLock(actionedUponNodeRef) == false) { - // Execute the implementation - executeImpl(action, actionedUponNodeRef); + // Execute the implementation + executeImpl(action, actionedUponNodeRef); } else { - if (logger.isWarnEnabled() == true) - { - logger.warn("Action (" + action.getActionDefinitionName() + - ") ignored because actioned upon node (" + actionedUponNodeRef.toString() + - ") is locked."); - } + if (logger.isWarnEnabled() == true) + { + logger.warn("Action (" + action.getActionDefinitionName() + + ") ignored because actioned upon node (" + actionedUponNodeRef.toString() + + ") is locked."); + } } } /** * Indicates whether a node has a lock. * - * @param nodeRef node reference - * @return boolean true if node has lock, false otherwise + * @param nodeRef node reference + * @return boolean true if node has lock, false otherwise */ private boolean hasLock(NodeRef nodeRef) { - return (lockService.getLockStatus(nodeRef) != LockStatus.NO_LOCK); + boolean result = false; + if (baseNodeService.exists(nodeRef) == true) + { + result = (lockService.getLockStatus(nodeRef) != LockStatus.NO_LOCK); + } + return result; } - + /** * Execute the action implementation * - * @param action the action + * @param action the action * @param actionedUponNodeRef the actioned upon node */ - protected abstract void executeImpl(Action action, NodeRef actionedUponNodeRef); - - /** - * Set the queueName which will execute this action - * if blank or null then the action will be executed on the "default" queue - * @param the name of the execution queue which should execute this action. - */ - public void setQueueName(String queueName) - { - this.queueName = queueName; - } + protected abstract void executeImpl(Action action, NodeRef actionedUponNodeRef); + + /** + * Set the queueName which will execute this action + * if blank or null then the action will be executed on the "default" queue + * @param the name of the execution queue which should execute this action. + */ + public void setQueueName(String queueName) + { + this.queueName = queueName; + } - public String getQueueName() { - return queueName; - } + public String getQueueName() { + return queueName; + } } diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index 22970fcf4b..11bf09d1ed 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java @@ -74,8 +74,10 @@ public class MailActionExecuter extends ActionExecuterAbstractBase public static final String PARAM_TO_MANY = "to_many"; public static final String PARAM_SUBJECT = "subject"; public static final String PARAM_TEXT = "text"; + public static final String PARAM_HTML = "html"; public static final String PARAM_FROM = "from"; public static final String PARAM_TEMPLATE = "template"; + public static final String PARAM_TEMPLATE_MODEL = "template_model"; /** * From address @@ -382,6 +384,13 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } } + // from person + NodeRef fromPerson = null; + if (! authService.isCurrentUserTheSystemUser()) + { + fromPerson = personService.getPerson(authService.getCurrentUserName()); + } + // set subject line message.setSubject((String)ruleAction.getParameterValue(PARAM_SUBJECT)); @@ -390,29 +399,69 @@ public class MailActionExecuter extends ActionExecuterAbstractBase NodeRef templateRef = (NodeRef)ruleAction.getParameterValue(PARAM_TEMPLATE); if (templateRef != null) { + Map suppliedModel = null; + if(ruleAction.getParameterValue(PARAM_TEMPLATE_MODEL) != null) + { + Object m = ruleAction.getParameterValue(PARAM_TEMPLATE_MODEL); + if(m instanceof Map) + { + suppliedModel = (Map)m; + } + else + { + logger.warn("Skipping unsupported email template model parameters of type " + + m.getClass().getName() + " : " + m.toString()); + } + } + // build the email template model - Map model = createEmailTemplateModel(actionedUponNodeRef); + Map model = createEmailTemplateModel(actionedUponNodeRef, suppliedModel, fromPerson); // process the template against the model text = templateService.processTemplate("freemarker", templateRef.toString(), model); } // set the text body of the message + + boolean isHTML = false; if (text == null) { text = (String)ruleAction.getParameterValue(PARAM_TEXT); } - message.setText(text); + + if (text != null) + { + // Note: only simplistic match here - expects = htmlPrefix.length() && + text.substring(0, htmlPrefix.length()).equalsIgnoreCase(htmlPrefix)) + { + isHTML = true; + } + } + else + { + text = (String)ruleAction.getParameterValue(PARAM_HTML); + if (text != null) + { + // assume HTML + isHTML = true; + } + } + + if (text != null) + { + message.setText(text, isHTML); + } // set the from address - NodeRef person = personService.getPerson(authService.getCurrentUserName()); - String fromActualUser = null; - if (person != null) + if (fromPerson != null) { - fromActualUser = (String) nodeService.getProperty(person, ContentModel.PROP_EMAIL); + fromActualUser = (String) nodeService.getProperty(fromPerson, ContentModel.PROP_EMAIL); } - if( fromActualUser != null && fromActualUser.length() != 0) + + if (fromActualUser != null && fromActualUser.length() != 0) { message.setFrom(fromActualUser); } @@ -430,7 +479,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } } }; - + try { // Send the message unless we are in "testMode" @@ -490,19 +539,25 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } /** - * @param ref The node representing the current document ref + * @param ref The node representing the current document ref (or null) * * @return Model map for email templates */ - private Map createEmailTemplateModel(NodeRef ref) + private Map createEmailTemplateModel(NodeRef ref, Map suppliedModel, NodeRef fromPerson) { Map model = new HashMap(8, 1.0f); - NodeRef person = personService.getPerson(authService.getCurrentUserName()); - model.put("person", new TemplateNode(person, serviceRegistry, null)); - model.put("document", new TemplateNode(ref, serviceRegistry, null)); - NodeRef parent = serviceRegistry.getNodeService().getPrimaryParent(ref).getParentRef(); - model.put("space", new TemplateNode(parent, serviceRegistry, null)); + if (fromPerson != null) + { + model.put("person", new TemplateNode(fromPerson, serviceRegistry, null)); + } + + if (ref != null) + { + model.put("document", new TemplateNode(ref, serviceRegistry, null)); + NodeRef parent = serviceRegistry.getNodeService().getPrimaryParent(ref).getParentRef(); + model.put("space", new TemplateNode(parent, serviceRegistry, null)); + } // current date/time is useful to have and isn't supplied by FreeMarker by default model.put("date", new Date()); @@ -513,6 +568,26 @@ public class MailActionExecuter extends ActionExecuterAbstractBase model.put("dateCompare", new DateCompareMethod()); model.put("url", new URLHelper(repoRemoteUrl)); + // if the caller specified a model, use it without overriding + if(suppliedModel != null && suppliedModel.size() > 0) + { + for(String key : suppliedModel.keySet()) + { + if(model.containsKey(key)) + { + if(logger.isDebugEnabled()) + { + logger.debug("Not allowing overwriting of built in model parameter " + key); + } + } + else + { + model.put(key, suppliedModel.get(key)); + } + } + } + + // all done return model; } @@ -528,6 +603,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase paramList.add(new ParameterDefinitionImpl(PARAM_TEXT, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_TEXT))); paramList.add(new ParameterDefinitionImpl(PARAM_FROM, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_FROM))); paramList.add(new ParameterDefinitionImpl(PARAM_TEMPLATE, DataTypeDefinition.NODE_REF, false, getParamDisplayLabel(PARAM_TEMPLATE), false, "ac-email-templates")); + paramList.add(new ParameterDefinitionImpl(PARAM_TEMPLATE_MODEL, DataTypeDefinition.ANY, false, getParamDisplayLabel(PARAM_TEMPLATE_MODEL), true)); } public void setTestMode(boolean testMode) diff --git a/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java index ca6bb2701b..cdddf7251e 100644 --- a/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ScriptActionExecuter.java @@ -35,6 +35,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.ScriptLocation; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.UrlUtil; /** * Action to execute a JavaScript. The script has access to the default model. @@ -154,7 +155,7 @@ public class ScriptActionExecuter extends ActionExecuterAbstractBase ScriptAction scriptAction = new ScriptAction(this.serviceRegistry, action, this.actionDefinition); model.put("action", scriptAction); - model.put("webApplicationContextUrl", sysAdminParams.getAlfrescoProtocol() + "://" + sysAdminParams.getAlfrescoHost() + ":" + sysAdminParams.getAlfrescoPort() + "/" + sysAdminParams.getAlfrescoContext()); + model.put("webApplicationContextUrl", UrlUtil.getAlfrescoUrl(sysAdminParams)); Object result = null; if (this.scriptLocation == null) diff --git a/source/java/org/alfresco/repo/action/test-action-services-context.xml b/source/java/org/alfresco/repo/action/test-action-services-context.xml index 8e9797a97c..feb7971bb9 100644 --- a/source/java/org/alfresco/repo/action/test-action-services-context.xml +++ b/source/java/org/alfresco/repo/action/test-action-services-context.xml @@ -6,6 +6,7 @@ false + 1000 diff --git a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java index 9a53f9824d..a953a7984b 100644 --- a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java +++ b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.activities.feed.cleanup.FeedCleaner; import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedEntity; import org.alfresco.repo.domain.activities.FeedControlDAO; @@ -38,22 +39,24 @@ import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.namespace.QName; -import org.springframework.extensions.surf.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.extensions.surf.util.ParameterCheck; /** * Activity Service Implementation * * @author janv */ -public class ActivityServiceImpl implements ActivityService +public class ActivityServiceImpl implements ActivityService, InitializingBean { private static final Log logger = LogFactory.getLog(ActivityServiceImpl.class); private ActivityFeedDAO feedDAO; private FeedControlDAO feedControlDAO; + private FeedCleaner feedCleaner; private AuthorityService authorityService; private TenantService tenantService; private SiteService siteService; @@ -83,6 +86,11 @@ public class ActivityServiceImpl implements ActivityService this.feedControlDAO = feedControlDAO; } + public void setFeedCleaner(FeedCleaner feedCleaner) + { + this.feedCleaner = feedCleaner; + } + public void setAuthorityService(AuthorityService authorityService) { this.authorityService = authorityService; @@ -104,6 +112,20 @@ public class ActivityServiceImpl implements ActivityService } + /*(non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + int feedCleanerMaxFeedItems = feedCleaner.getMaxFeedSize(); + if (maxFeedItems > feedCleanerMaxFeedItems) + { + logger.warn("Cannot retrieve more items than feed cleaner max items (overriding "+maxFeedItems+" to "+feedCleanerMaxFeedItems+")"); + maxFeedItems = feedCleanerMaxFeedItems; + } + } + + /* (non-Javadoc) * @see org.alfresco.service.cmr.activities.ActivityService#postActivity(java.lang.String, java.lang.String, java.lang.String, java.lang.String) */ @@ -152,13 +174,37 @@ public class ActivityServiceImpl implements ActivityService * @see org.alfresco.service.cmr.activities.ActivityService#getUserFeedEntries(java.lang.String, java.lang.String, java.lang.String, boolean, boolean) */ public List getUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers) + { + List activityFeedEntries = new ArrayList(); + + try + { + List activityFeeds = getUserFeedEntries(feedUserId, format, siteId, excludeThisUser, excludeOtherUsers, -1); + + if (activityFeeds != null) + { + for (ActivityFeedEntity activityFeed : activityFeeds) + { + activityFeedEntries.add(activityFeed.getJSONString()); + } + } + } + catch (JSONException je) + { + AlfrescoRuntimeException are = new AlfrescoRuntimeException("Unable to get user feed entries: " + je.getMessage()); + logger.error(are); + throw are; + } + + return activityFeedEntries; + } + + public List getUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId) { // NOTE: siteId is optional ParameterCheck.mandatoryString("feedUserId", feedUserId); ParameterCheck.mandatoryString("format", format); - List activityFeedEntries = new ArrayList(); - if (! userNamesAreCaseSensitive) { feedUserId = feedUserId.toLowerCase(); @@ -173,27 +219,21 @@ public class ActivityServiceImpl implements ActivityService throw new AccessDeniedException("Unable to get user feed entries for '" + feedUserId + "' - currently logged in as '" + currentUser +"'"); } + List result = new ArrayList(); + try { - List activityFeeds = null; if (siteId != null) { siteId = tenantService.getName(siteId); } - activityFeeds = feedDAO.selectUserFeedEntries(feedUserId, format, siteId, excludeThisUser, excludeOtherUsers); + List activityFeeds = feedDAO.selectUserFeedEntries(feedUserId, format, siteId, excludeThisUser, excludeOtherUsers, minFeedId, maxFeedItems); - int count = 0; for (ActivityFeedEntity activityFeed : activityFeeds) { - count++; - if (count > maxFeedItems) - { - break; - } - activityFeed.setSiteNetwork(tenantService.getBaseName(activityFeed.getSiteNetwork())); - activityFeedEntries.add(activityFeed.getJSONString()); + result.add(activityFeed); } } catch (SQLException se) @@ -202,14 +242,8 @@ public class ActivityServiceImpl implements ActivityService logger.error(are); throw are; } - catch (JSONException je) - { - AlfrescoRuntimeException are = new AlfrescoRuntimeException("Unable to get user feed entries: " + je.getMessage()); - logger.error(are); - throw are; - } - return activityFeedEntries; + return result; } /* (non-Javadoc) @@ -235,17 +269,10 @@ public class ActivityServiceImpl implements ActivityService siteId = tenantService.getName(siteId); - List activityFeeds = feedDAO.selectSiteFeedEntries(siteId, format); + List activityFeeds = feedDAO.selectSiteFeedEntries(siteId, format, maxFeedItems); - int count = 0; for (ActivityFeedEntity activityFeed : activityFeeds) { - count++; - if (count > maxFeedItems) - { - break; - } - activityFeed.setSiteNetwork(tenantService.getBaseName(activityFeed.getSiteNetwork())); activityFeedEntries.add(activityFeed.getJSONString()); } @@ -266,6 +293,14 @@ public class ActivityServiceImpl implements ActivityService return activityFeedEntries; } + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#getMaxFeedItems() + */ + public int getMaxFeedItems() + { + return this.maxFeedItems; + } + /* (non-Javadoc) * @see org.alfresco.service.cmr.activities.ActivityService#setFeedControl(org.alfresco.service.cmr.activities.FeedControl) */ @@ -409,6 +444,7 @@ public class ActivityServiceImpl implements ActivityService return userId; } + /* private FeedControl getTenantFeedControl(FeedControl feedControl) { // TODO @@ -420,4 +456,5 @@ public class ActivityServiceImpl implements ActivityService // TODO return null; } + */ } diff --git a/source/java/org/alfresco/repo/activities/SiteActivityTest.java b/source/java/org/alfresco/repo/activities/SiteActivityTest.java index 8211be8950..2bb74dd128 100644 --- a/source/java/org/alfresco/repo/activities/SiteActivityTest.java +++ b/source/java/org/alfresco/repo/activities/SiteActivityTest.java @@ -27,6 +27,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.activities.feed.FeedGenerator; import org.alfresco.repo.activities.feed.local.LocalFeedTaskProcessor; import org.alfresco.repo.activities.post.lookup.PostLookup; +import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.site.SiteModel; @@ -36,12 +37,12 @@ import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.cmr.site.SiteVisibility; -import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.Scheduler; import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Simple Activity Service unit test using site (membership) activities @@ -52,7 +53,14 @@ public class SiteActivityTest extends TestCase { private static Log logger = LogFactory.getLog(SiteActivityTest.class); - private static ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); + private static final String[] CONFIG_LOCATIONS = + { + "classpath:alfresco/application-context.xml" + //, "classpath:alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml" + }; + + private static ApplicationContext applicationContext = new ClassPathXmlApplicationContext( + SiteActivityTest.CONFIG_LOCATIONS); private SiteService siteService; private ActivityService activityService; @@ -112,10 +120,20 @@ public class SiteActivityTest extends TestCase this.authenticationService = (MutableAuthenticationService)applicationContext.getBean("AuthenticationService"); this.personService = (PersonService)applicationContext.getBean("PersonService"); - this.postLookup = (PostLookup)applicationContext.getBean("postLookup"); - this.feedGenerator = (FeedGenerator)applicationContext.getBean("feedGenerator"); + LocalFeedTaskProcessor feedProcessor = null; + + // alternative: would need to add subsystem context to config location (see above) + //this.postLookup = (PostLookup)applicationContext.getBean("postLookup"); + //this.feedGenerator = (FeedGenerator)applicationContext.getBean("feedGenerator"); + //feedProcessor = (LocalFeedTaskProcessor)applicationContext.getBean("feedTaskProcessor"); + + ChildApplicationContextFactory activitiesFeed = (ChildApplicationContextFactory)applicationContext.getBean("ActivitiesFeed"); + ApplicationContext activitiesFeedCtx = activitiesFeed.getApplicationContext(); + this.postLookup = (PostLookup)activitiesFeedCtx.getBean("postLookup"); + this.feedGenerator = (FeedGenerator)activitiesFeedCtx.getBean("feedGenerator"); + feedProcessor = (LocalFeedTaskProcessor)activitiesFeedCtx.getBean("feedTaskProcessor"); + - LocalFeedTaskProcessor feedProcessor = (LocalFeedTaskProcessor)applicationContext.getBean("feedTaskProcessor"); List templateSearchPaths = new ArrayList(1); templateSearchPaths.add(TEST_TEMPLATES_LOCATION); feedProcessor.setTemplateSearchPaths(templateSearchPaths); diff --git a/source/java/org/alfresco/service/cmr/admin/AdminService.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifier.java similarity index 70% rename from source/java/org/alfresco/service/cmr/admin/AdminService.java rename to source/java/org/alfresco/repo/activities/feed/FeedNotifier.java index ce8c9e38ec..84823e9a75 100644 --- a/source/java/org/alfresco/service/cmr/admin/AdminService.java +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifier.java @@ -16,20 +16,19 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -package org.alfresco.service.cmr.admin; +package org.alfresco.repo.activities.feed; -import java.util.List; - -import org.alfresco.service.PublicService; /** - * General administration tasks and system information. + * Interface for feed notifier * - * @since 1.2 - * @author Derek Hulley + * @since 3.5 */ -@PublicService -public interface AdminService +public interface FeedNotifier { -// public List getPatches(); + /** + * + * @param repeatIntervalMins system-wide job repeat interval (in minutes) + */ + public void execute(int repeatIntervalMins); } diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java new file mode 100644 index 0000000000..3d09386cf3 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.activities.feed; + +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.dictionary.RepositoryLocation; +import org.alfresco.repo.domain.activities.ActivityFeedEntity; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.LockAcquisitionException; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ModelUtil; +import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.UrlUtil; +import org.alfresco.util.VmShutdownListener; +import org.alfresco.util.VmShutdownListener.VmShutdownException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Implementation of the Activity Feed Notifier component + * + * Note: currently implemented to email activities stored in JSON format + * + * @since 3.5 + */ +public class FeedNotifierImpl implements FeedNotifier +{ + private static Log logger = LogFactory.getLog(FeedNotifierImpl.class); + + private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "ActivityFeedNotifier"); + private static final long LOCK_TTL = 30000L; + private static ThreadLocal> lockThreadLocal = new ThreadLocal>(); + + private static VmShutdownListener vmShutdownListener = new VmShutdownListener(FeedNotifierImpl.class.getName()); + + private static final String MSG_EMAIL_SUBJECT = "activities.feed.notifier.email.subject"; + + private ActivityService activityService; + private PersonService personService; + private NodeService nodeService; + private FileFolderService fileFolderService; + private ActionService actionService; + private SearchService searchService; + private NamespaceService namespaceService; + private SiteService siteService; + private JobLockService jobLockService; + private TransactionService transactionService; + private AuthenticationContext authenticationContext; + private SysAdminParams sysAdminParams; + private RepoAdminService repoAdminService; + private List excludedEmailSuffixes; + + private RepositoryLocation feedEmailTemplateLocation; + + + public void setActivityService(ActivityService activityService) + { + this.activityService = activityService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setJobLockService(JobLockService jobLockService) + { + this.jobLockService = jobLockService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setFeedEmailTemplateLocation(RepositoryLocation feedEmailTemplateLocation) + { + this.feedEmailTemplateLocation = feedEmailTemplateLocation; + } + + public void setSysAdminParams(SysAdminParams sysAdminParams) + { + this.sysAdminParams = sysAdminParams; + } + + public void setRepoAdminService(RepoAdminService repoAdminService) + { + this.repoAdminService = repoAdminService; + } + + public void setExcludedEmailSuffixes(List excludedEmailSuffixes) + { + this.excludedEmailSuffixes = excludedEmailSuffixes; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + private void checkProperties() + { + PropertyCheck.mandatory(this, "activityService", activityService); + PropertyCheck.mandatory(this, "personService", personService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); + PropertyCheck.mandatory(this, "actionService", actionService); + PropertyCheck.mandatory(this, "searchService", searchService); + PropertyCheck.mandatory(this, "namespaceService", namespaceService); + PropertyCheck.mandatory(this, "siteService", siteService); + PropertyCheck.mandatory(this, "jobLockService", jobLockService); + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "authenticationContext", authenticationContext); + PropertyCheck.mandatory(this, "sysAdminParams", sysAdminParams); + PropertyCheck.mandatory(this, "feedEmailTemplateLocation", feedEmailTemplateLocation); + } + + public void execute(int repeatIntervalMins) + { + checkProperties(); + + // Bypass if the system is in read-only mode + if (transactionService.isReadOnly()) + { + if (logger.isDebugEnabled()) + { + logger.debug("Activities email notification bypassed; the system is read-only"); + } + return; + } + + try + { + if (logger.isTraceEnabled()) + { + logger.trace("Activities email notification started"); + } + + refreshLock(); + executeInternal(repeatIntervalMins); + + // Done + if (logger.isTraceEnabled()) + { + logger.trace("Activities email notification completed"); + } + } + catch (LockAcquisitionException e) + { + // Job being done by another process + if (logger.isDebugEnabled()) + { + logger.debug("Activities email notification already underway"); + } + } + catch (VmShutdownException e) + { + // Aborted + if (logger.isDebugEnabled()) + { + logger.debug("Activities email notification aborted"); + } + } + finally + { + releaseLock(); + } + } + + private void executeInternal(final int repeatIntervalMins) + { + final NodeRef emailTemplateRef = getEmailTemplateRef(); + + if (emailTemplateRef == null) + { + return; + } + + final String shareUrl = UrlUtil.getShareUrl(sysAdminParams); + + if (logger.isDebugEnabled()) + { + logger.debug("Share URL configured as: "+shareUrl); + } + + int userCnt = 0; + int feedEntryCnt = 0; + + long startTime = System.currentTimeMillis(); + + try + { + final String subjectText = buildSubjectText(startTime); + + Set people = personService.getAllPeople(); + + // local cache for this execution + final Map siteNames = new HashMap(10); + + for (final NodeRef personNodeRef : people) + { + refreshLock(); + + try + { + final RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper(); + txHelper.setMaxRetries(0); + + Pair result = txHelper.doInTransaction(new RetryingTransactionCallback>() + { + public Pair execute() throws Throwable + { + return prepareAndSendEmail(personNodeRef, emailTemplateRef, subjectText, siteNames, shareUrl, repeatIntervalMins); + } + }, true, true); + + if (result != null) + { + int entryCnt = result.getFirst(); + final long maxFeedId = result.getSecond(); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + + Long currentMaxFeedId = (Long)nodeService.getProperty(personNodeRef, ContentModel.PROP_EMAIL_FEED_ID); + if ((currentMaxFeedId == null) || (currentMaxFeedId < maxFeedId)) + { + nodeService.setProperty(personNodeRef, ContentModel.PROP_EMAIL_FEED_ID, maxFeedId); + } + + return null; + } + }, false, true); + + + userCnt++; + feedEntryCnt += entryCnt; + } + } + catch (InvalidNodeRefException inre) + { + // skip this person - eg. no longer exists ? + logger.warn("Skip feed notification for user ("+personNodeRef+"): " + inre.getMessage()); + } + } + } + catch (Throwable e) + { + // If the VM is shutting down, then ignore + if (vmShutdownListener.isVmShuttingDown()) + { + // Ignore + } + else + { + logger.error("Exception during notification of feeds", e); + } + } + finally + { + // assume sends are synchronous - hence bump up to last max feed id + if (userCnt > 0) + { + if (logger.isInfoEnabled()) + { + // TODO i18n of info message + StringBuilder sb = new StringBuilder(); + sb.append("Notified ").append(userCnt).append(" user").append(userCnt != 1 ? "s" : ""); + sb.append(" of ").append(feedEntryCnt).append(" activity feed entr").append(feedEntryCnt != 1 ? "ies" : "y"); + sb.append(" (in ").append(System.currentTimeMillis()-startTime).append(" msecs)"); + logger.info(sb.toString()); + } + } + else + { + if (logger.isTraceEnabled()) + { + logger.trace("Nothing to send since no new user activities found"); + } + } + } + } + + protected Pair prepareAndSendEmail(final NodeRef personNodeRef, NodeRef emailTemplateRef, + String subjectText, Map siteNames, + String shareUrl, int repeatIntervalMins) + { + Map personProps = nodeService.getProperties(personNodeRef); + + String feedUserId = (String)personProps.get(ContentModel.PROP_USERNAME); + String emailAddress = (String)personProps.get(ContentModel.PROP_EMAIL); + Boolean emailFeedDisabled = (Boolean)personProps.get(ContentModel.PROP_EMAIL_FEED_DISABLED); + + if (skipUser(emailFeedDisabled, feedUserId, emailAddress, excludedEmailSuffixes)) + { + // skip + return null; + } + + // where did we get up to ? + Long emailFeedDBID = (Long)personProps.get(ContentModel.PROP_EMAIL_FEED_ID); + if (emailFeedDBID != null) + { + // increment min feed id + emailFeedDBID++; + } + else + { + emailFeedDBID = -1L; + } + + + // own + others (note: template can be changed to filter out user's own activities if needed) + List feedEntries = activityService.getUserFeedEntries(feedUserId, FeedTaskProcessor.FEED_FORMAT_JSON, null, false, false, emailFeedDBID); + + if (feedEntries.size() > 0) + { + long userMaxFeedId = -1L; + + Map model = new HashMap(); + List> activityFeedModels = new ArrayList>(); + + for (ActivityFeedEntity feedEntry : feedEntries) + { + Map map = null; + try + { + map = feedEntry.getModel(); + activityFeedModels.add(map); + + String siteId = feedEntry.getSiteNetwork(); + addSiteName(siteId, siteNames); + + long feedId = feedEntry.getId(); + if (feedId > userMaxFeedId) + { + userMaxFeedId = feedId; + } + } + catch (JSONException je) + { + // skip this feed entry + logger.warn("Skip feed entry for user ("+feedUserId+"): " + je.getMessage()); + continue; + } + } + + if (activityFeedModels.size() > 0) + { + model.put("activities", activityFeedModels); + model.put("siteTitles", siteNames); + model.put("repeatIntervalMins", repeatIntervalMins); + model.put("feedItemsMax", activityService.getMaxFeedItems()); + model.put("feedItemsCount", activityFeedModels.size()); + + // add Share info to model + model.put(TemplateService.KEY_SHARE_URL, shareUrl); + model.put(TemplateService.KEY_PRODUCT_NAME, ModelUtil.getProductName(repoAdminService)); + + Map personPrefixProps = new HashMap(personProps.size()); + for (QName propQName : personProps.keySet()) + { + try + { + String propPrefix = propQName.toPrefixString(namespaceService); + personPrefixProps.put(propPrefix, personProps.get(propQName)); + } + catch (NamespaceException ne) + { + // ignore properties that do not have a registered namespace + logger.warn("Ignoring property '" + propQName + "' as it's namespace is not registered"); + } + } + + model.put("personProps", personPrefixProps); + + // send + sendMail(emailTemplateRef, emailAddress, subjectText, model); + + return new Pair(activityFeedModels.size(), userMaxFeedId); + } + } + + return null; + } + + protected void sendMail(NodeRef emailTemplateRef, String emailAddress, String subjectText, Map model) + { + ParameterCheck.mandatoryString("emailAddress", emailAddress); + + Action mail = actionService.createAction(MailActionExecuter.NAME); + + mail.setParameterValue(MailActionExecuter.PARAM_TO, emailAddress); + mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, subjectText); + + //mail.setParameterValue(MailActionExecuter.PARAM_TEXT, buildMailText(emailTemplateRef, model)); + mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, emailTemplateRef); + mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable)model); + + actionService.executeAction(mail, null); + } + + protected String buildSubjectText(long currentTime) + { + return I18NUtil.getMessage(MSG_EMAIL_SUBJECT, ModelUtil.getProductName(repoAdminService)); + } + + protected NodeRef getEmailTemplateRef() + { + StoreRef store = feedEmailTemplateLocation.getStoreRef(); + String xpath = feedEmailTemplateLocation.getPath(); + + if (! feedEmailTemplateLocation.getQueryLanguage().equals(SearchService.LANGUAGE_XPATH)) + { + logger.warn("Cannot find the activities email template - repository location query language is not 'xpath': "+feedEmailTemplateLocation.getQueryLanguage()); + return null; + } + + List nodeRefs = searchService.selectNodes(nodeService.getRootNode(store), xpath, null, namespaceService, false); + if (nodeRefs.size() != 1) + { + logger.warn("Cannot find the activities email template: "+xpath); + return null; + } + + return fileFolderService.getLocalizedSibling(nodeRefs.get(0)); + } + + protected void addSiteName(String siteId, Map siteNames) + { + if (siteId == null) + { + return; + } + + String siteName = siteNames.get(siteId); + if (siteName == null) + { + SiteInfo site = siteService.getSite(siteId); + if (site == null) + { + return; + } + + String siteTitle = site.getTitle(); + if (siteTitle != null && siteTitle.length() > 0) + { + siteName = siteTitle; + } + else + { + siteName = siteId; + } + + siteNames.put(siteId, siteName); + } + } + + protected boolean skipUser(Boolean emailFeedDisabled, String feedUserId, String emailAddress, List excludedEmailSuffixes) + { + if ((emailFeedDisabled != null) && (emailFeedDisabled == true)) + { + return true; + } + + if (authenticationContext.isSystemUserName(feedUserId) || authenticationContext.isGuestUserName(feedUserId)) + { + // skip "guest" or "System" user + return true; + } + + if ((emailAddress == null) || (emailAddress.length() <= 0)) + { + // skip user that does not have an email address + if (logger.isDebugEnabled()) + { + logger.debug("Skip for '"+feedUserId+"' since they have no email address set"); + } + return true; + } + + String lowerEmailAddress = emailAddress.toLowerCase(); + for (String excludedEmailSuffix : excludedEmailSuffixes) + { + if (lowerEmailAddress.endsWith(excludedEmailSuffix.toLowerCase())) + { + // skip user whose email matches exclude suffix + if (logger.isDebugEnabled()) + { + logger.debug("Skip for '"+feedUserId+"' since email address is excluded ("+emailAddress+")"); + } + return true; + } + } + + return false; + } + + /** + * Lazily update the job lock + */ + private void refreshLock() + { + Pair lockPair = lockThreadLocal.get(); + if (lockPair == null) + { + String lockToken = jobLockService.getLock(LOCK_QNAME, LOCK_TTL); + Long lastLock = new Long(System.currentTimeMillis()); + // We have not locked before + lockPair = new Pair(lastLock, lockToken); + lockThreadLocal.set(lockPair); + } + else + { + long now = System.currentTimeMillis(); + long lastLock = lockPair.getFirst().longValue(); + String lockToken = lockPair.getSecond(); + // Only refresh the lock if we are past a threshold + if (now - lastLock > (long)(LOCK_TTL/2L)) + { + jobLockService.refreshLock(lockToken, LOCK_QNAME, LOCK_TTL); + lastLock = System.currentTimeMillis(); + lockPair = new Pair(lastLock, lockToken); + } + } + } + + /** + * Release the lock after the job completes + */ + private void releaseLock() + { + Pair lockPair = lockThreadLocal.get(); + if (lockPair != null) + { + // We can't release without a token + try + { + jobLockService.releaseLock(lockPair.getSecond(), LOCK_QNAME); + } + finally + { + // Reset + lockThreadLocal.set(null); + } + } + // else: We can't release without a token + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierJob.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierJob.java new file mode 100644 index 0000000000..af79b56791 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierJob.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.activities.feed; + +import java.util.List; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.Tenant; +import org.alfresco.repo.tenant.TenantAdminService; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.SimpleTrigger; +import org.quartz.Trigger; + +/** + * Executes scheduled feed email notifier quartz-job - refer to scheduled-jobs-context.xml + * + * @since 3.5 + */ +public class FeedNotifierJob implements Job +{ + private static final String KEY_FEED_NOTIFIER = "feedNotifier"; + private static final String KEY_TENANT_ADMIN_SERVICE = "tenantAdminService"; + + /** + * Calls the feed notifier to do its work + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + + final FeedNotifier feedNotifier = (FeedNotifier)jobData.get(KEY_FEED_NOTIFIER); + final TenantAdminService tenantAdminService = (TenantAdminService)jobData.get(KEY_TENANT_ADMIN_SERVICE); + + Long repeatInterval = null; + Trigger trigger = context.getTrigger(); + if (trigger instanceof SimpleTrigger) + { + repeatInterval = ((SimpleTrigger)trigger).getRepeatInterval(); + } + + final int repeatIntervalMins = new Long(repeatInterval == null ? 0L : repeatInterval / 1000 / 60).intValue(); + + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + feedNotifier.execute(repeatIntervalMins); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + if ((tenantAdminService != null) && tenantAdminService.isEnabled()) + { + List tenants = tenantAdminService.getAllTenants(); + for (Tenant tenant : tenants) + { + String tenantDomain = tenant.getTenantDomain(); + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + feedNotifier.execute(repeatIntervalMins); + return null; + } + }, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); + } + } + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java b/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java index db8902be63..1dddad8752 100644 --- a/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java +++ b/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java @@ -65,8 +65,21 @@ public abstract class FeedTaskProcessor { private static final Log logger = LogFactory.getLog(FeedTaskProcessor.class); - private static final String defaultFormat = "text"; - private static final String[] formats = {"atomentry", "rss", "json", "html", "xml", defaultFormat}; + public static final String FEED_FORMAT_JSON = "json"; + public static final String FEED_FORMAT_ATOMENTRY = "atomentry"; + public static final String FEED_FORMAT_HTML = "html"; + public static final String FEED_FORMAT_RSS = "rss"; + public static final String FEED_FORMAT_TEXT = "text"; + public static final String FEED_FORMAT_XML = "xml"; + + private static final String defaultFormat = FEED_FORMAT_TEXT; + + private static final String[] formats = {FEED_FORMAT_ATOMENTRY, + FEED_FORMAT_RSS, + FEED_FORMAT_JSON, + FEED_FORMAT_HTML, + FEED_FORMAT_XML, + defaultFormat}; private static final String URL_SERVICE_SITES = "/api/sites"; private static final String URL_MEMBERSHIPS = "/memberships"; @@ -74,14 +87,14 @@ public abstract class FeedTaskProcessor private static final String URL_SERVICE_TEMPLATES = "/api/activities/templates"; private static final String URL_SERVICE_TEMPLATE = "/api/activities/template"; - + public void process(int jobTaskNode, long minSeq, long maxSeq, RepoCtx ctx) throws Exception { long startTime = System.currentTimeMillis(); if (logger.isDebugEnabled()) { - logger.debug(">>> Process: jobTaskNode '" + jobTaskNode + "' from seq '" + minSeq + "' to seq '" + maxSeq + "' on this node from grid job."); + logger.debug("Process: jobTaskNode '" + jobTaskNode + "' from seq '" + minSeq + "' to seq '" + maxSeq + "' on this node from grid job."); } ActivityPostEntity selector = new ActivityPostEntity(); @@ -99,7 +112,7 @@ public abstract class FeedTaskProcessor { activityPosts = selectPosts(selector); - if (logger.isDebugEnabled()) { logger.debug(">>> Process: " + activityPosts.size() + " activity posts"); } + if (logger.isDebugEnabled()) { logger.debug("Process: " + activityPosts.size() + " activity posts"); } Configuration cfg = getFreemarkerConfiguration(ctx); @@ -177,7 +190,7 @@ public abstract class FeedTaskProcessor if (fmTemplates.size() == 0) { - logger.error(">>> Skipping activity post " + activityPost.getId() + " since no specific/generic templates for activityType: " + activityType ); + logger.error("Skipping activity post " + activityPost.getId() + " since no specific/generic templates for activityType: " + activityType ); updatePostStatus(activityPost.getId(), ActivityPostEntity.STATUS.ERROR); continue; } @@ -189,15 +202,15 @@ public abstract class FeedTaskProcessor } catch(JSONException je) { - logger.error(">>> Skipping activity post " + activityPost.getId() + " due to invalid activity data: " + je); + logger.error("Skipping activity post " + activityPost.getId() + " due to invalid activity data: " + je); updatePostStatus(activityPost.getId(), ActivityPostEntity.STATUS.ERROR); continue; } String thisSite = activityPost.getSiteNetwork(); - model.put("activityType", activityPost.getActivityType()); - model.put("siteNetwork", thisSite); + model.put(ActivityFeedEntity.KEY_ACTIVITY_FEED_TYPE, activityPost.getActivityType()); + model.put(ActivityFeedEntity.KEY_ACTIVITY_FEED_SITE, thisSite); model.put("userId", activityPost.getUserId()); model.put("id", activityPost.getId()); model.put("date", activityPost.getPostDate()); // post date rather than time that feed is generated @@ -225,7 +238,7 @@ public abstract class FeedTaskProcessor } catch(Exception e) { - logger.error(">>> Skipping activity post " + activityPost.getId() + " since failed to get site members: " + e); + logger.error("Skipping activity post " + activityPost.getId() + " since failed to get site members: " + e); updatePostStatus(activityPost.getId(), ActivityPostEntity.STATUS.ERROR); continue; } @@ -238,7 +251,7 @@ public abstract class FeedTaskProcessor if (logger.isTraceEnabled()) { - logger.trace(">>> Process: " + connectedUsers.size() + " candidate connections for activity post " + activityPost.getId()); + logger.trace("Process: " + connectedUsers.size() + " candidate connections for activity post " + activityPost.getId()); } int excludedConnections = 0; @@ -291,7 +304,7 @@ public abstract class FeedTaskProcessor feed.setPostUserId(postingUserId); feed.setActivityType(activityType); - if (formatFound.equals("json")) + if (formatFound.equals(FeedTaskProcessor.FEED_FORMAT_JSON)) { // allows generic JSON template to simply pass straight through model.put("activityData", activityPost.getActivityData()); @@ -337,7 +350,7 @@ public abstract class FeedTaskProcessor if (logger.isDebugEnabled()) { - logger.debug(">>> Processed: " + (connectedUsers.size() - excludedConnections) + " connections for activity post " + activityPost.getId() + " (excluded " + excludedConnections + ")"); + logger.debug("Processed: " + (connectedUsers.size() - excludedConnections) + " connections for activity post " + activityPost.getId() + " (excluded " + excludedConnections + ")"); } } finally @@ -353,7 +366,13 @@ public abstract class FeedTaskProcessor } finally { - logger.info(">>> Generated " + totalGenerated + " activity feed entries for " + (activityPosts == null ? 0 : activityPosts.size()) + " activity posts (in " + (System.currentTimeMillis() - startTime) + " msecs)"); + int postCnt = activityPosts == null ? 0 : activityPosts.size(); + + // TODO i18n info message + StringBuilder sb = new StringBuilder(); + sb.append("Generated ").append(totalGenerated).append(" activity feed entr").append(totalGenerated == 1 ? "y" : "ies"); + sb.append(" for ").append(postCnt).append(" activity post").append(postCnt != 1 ? "s" : "").append(" (in ").append(System.currentTimeMillis() - startTime).append(" msecs)"); + logger.info(sb.toString()); } } @@ -378,7 +397,7 @@ public abstract class FeedTaskProcessor if (logger.isDebugEnabled()) { - logger.debug(">>> Request URI: " + url.toURI()); + logger.debug("Request URI: " + url.toURI()); } HttpURLConnection conn = (HttpURLConnection)url.openConnection(); @@ -412,7 +431,7 @@ public abstract class FeedTaskProcessor if (logger.isDebugEnabled()) { int responseCode = conn.getResponseCode(); - logger.debug(">>> Response code: " + responseCode); + logger.debug("Response code: " + responseCode); } } finally @@ -632,7 +651,7 @@ public abstract class FeedTaskProcessor { if (logger.isDebugEnabled()) { - logger.debug(">>> Add template '" + templateToAdd + "' for type '" + activityType + "'"); + logger.debug("Add template '" + templateToAdd + "' for type '" + activityType + "'"); } fmTemplates.add(templateToAdd); } @@ -676,7 +695,7 @@ public abstract class FeedTaskProcessor if (logger.isDebugEnabled()) { - logger.debug(">>> getURL: " + sb.toString()); + logger.debug("getURL: " + sb.toString()); } return new URL(sb.toString()); diff --git a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java index 2d96bcb4ed..f62802b336 100644 --- a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java +++ b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java @@ -19,12 +19,25 @@ package org.alfresco.repo.activities.feed.cleanup; import java.sql.SQLException; +import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedEntity; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.util.PropertyCheck; import org.alfresco.util.VmShutdownListener; @@ -35,10 +48,13 @@ import org.quartz.JobExecutionException; /** * The feed cleaner component is responsible for purging 'obsolete' activity feed entries */ -public class FeedCleaner +public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy { private static Log logger = LogFactory.getLog(FeedCleaner.class); + private static String KEY_DELETED_SITE_IDS = "feedCleaner.deletedSites"; + private static String KEY_DELETED_USER_IDS = "feedCleaner.deletedUsers"; + private static VmShutdownListener vmShutdownListener = new VmShutdownListener(FeedCleaner.class.getName()); private int maxAgeMins = 0; @@ -48,6 +64,12 @@ public class FeedCleaner private ActivityFeedDAO feedDAO; private SiteService siteService; + private NodeService nodeService; + private PolicyComponent policyComponent; + + private FeedCleanerDeleteSiteTransactionListener deleteSiteTransactionListener; + private FeedCleanerDeletePersonTransactionListener deletePersonTransactionListener; + public void setFeedDAO(ActivityFeedDAO feedDAO) { @@ -59,6 +81,16 @@ public class FeedCleaner this.siteService = siteService; } + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + public void setMaxAgeMins(int mins) { this.maxAgeMins = mins; @@ -70,6 +102,12 @@ public class FeedCleaner this.maxFeedSize = size; } + public int getMaxFeedSize() + { + return this.maxFeedSize; + } + + /** * Perform basic checks to ensure that the necessary dependencies were injected. */ @@ -83,7 +121,21 @@ public class FeedCleaner logger.warn("Neither maxAgeMins or maxFeedSize set - feeds will not be cleaned"); } } + + public void init() + { + policyComponent.bindClassBehaviour(BeforeDeleteNodePolicy.QNAME, + ContentModel.TYPE_PERSON, + new JavaBehaviour(this, "beforeDeleteNodePerson")); + deletePersonTransactionListener = new FeedCleanerDeletePersonTransactionListener(); + + policyComponent.bindClassBehaviour(BeforeDeleteNodePolicy.QNAME, + SiteModel.TYPE_SITE, + new JavaBehaviour(this, "beforeDeleteNodeSite")); + + deleteSiteTransactionListener = new FeedCleanerDeleteSiteTransactionListener(); + } public int execute() throws JobExecutionException { checkProperties(); @@ -140,11 +192,11 @@ public class FeedCleaner if ((feedUserId == null) || (feedUserId.length() == 0)) { - feedToClean = feedDAO.selectSiteFeedEntries(siteId, format); + feedToClean = feedDAO.selectSiteFeedEntries(siteId, format, -1); } else { - feedToClean = feedDAO.selectUserFeedEntries(feedUserId, format, null, false, false); + feedToClean = feedDAO.selectUserFeedEntries(feedUserId, format, null, false, false, -1L, -1); if (siteService != null) { @@ -225,4 +277,95 @@ public class FeedCleaner return (maxAgeDeletedCount + maxSizeDeletedCount); } + + // behaviours + + public void beforeDeleteNode(NodeRef nodeRef) + { + // dummy + } + + @SuppressWarnings("unchecked") + public void beforeDeleteNodePerson(NodeRef personNodeRef) + { + String userId = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + + Set deletedUserIds = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_USER_IDS); + if (deletedUserIds == null) + { + deletedUserIds = Collections.newSetFromMap(new ConcurrentHashMap()); // Java 6 + AlfrescoTransactionSupport.bindResource(KEY_DELETED_USER_IDS, deletedUserIds); + } + + deletedUserIds.add(userId); + + AlfrescoTransactionSupport.bindListener(deletePersonTransactionListener); + } + + @SuppressWarnings("unchecked") + public void beforeDeleteNodeSite(NodeRef siteNodeRef) + { + String siteId = (String)nodeService.getProperty(siteNodeRef, ContentModel.PROP_NAME); + + Set deletedSiteIds = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_SITE_IDS); + if (deletedSiteIds == null) + { + deletedSiteIds = Collections.newSetFromMap(new ConcurrentHashMap()); // Java 6 + AlfrescoTransactionSupport.bindResource(KEY_DELETED_SITE_IDS, deletedSiteIds); + } + + deletedSiteIds.add(siteId); + + AlfrescoTransactionSupport.bindListener(deleteSiteTransactionListener); + } + + class FeedCleanerDeleteSiteTransactionListener extends TransactionListenerAdapter + { + @SuppressWarnings("unchecked") + @Override + public void afterCommit() + { + Set deletedSiteIds = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_SITE_IDS); + if (deletedSiteIds != null) + { + for (String siteId : deletedSiteIds) + { + try + { + // Since we are in post-commit, we do best-effort + feedDAO.deleteSiteFeedEntries(siteId); + } + catch (SQLException e) + { + logger.error("Activities feed cleanup for site '"+siteId+"' failed: ", e); + } + } + } + } + } + + class FeedCleanerDeletePersonTransactionListener extends TransactionListenerAdapter + { + @SuppressWarnings("unchecked") + @Override + public void afterCommit() + { + Set deletedUserIds = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_USER_IDS); + if (deletedUserIds != null) + { + for (String userId : deletedUserIds) + { + try + { + // Since we are in post-commit, we do best-effort + feedDAO.deleteUserFeedEntries(userId); + } + catch (SQLException e) + { + logger.error("Activities feed cleanup for user '"+userId+"' failed: ", e); + } + } + } + } + } } diff --git a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanerTest.java b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanerTest.java index 8c8ce6236d..0cd58c68fe 100644 --- a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanerTest.java +++ b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanerTest.java @@ -24,13 +24,16 @@ import java.util.Date; import junit.framework.TestCase; +import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedEntity; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.cmr.site.SiteVisibility; import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.PropertyMap; import org.springframework.context.ApplicationContext; /** @@ -45,12 +48,31 @@ public class FeedCleanerTest extends TestCase private ActivityFeedDAO feedDAO; private FeedCleaner cleaner; private SiteService siteService; + private PersonService personService; protected RetryingTransactionHelper transactionHelper; + private static final String TEST_SITE = "testSite"; + + private static final String TEST_SITE_1 = TEST_SITE+"1"; + private static final String TEST_SITE_2 = TEST_SITE+"2"; + private static final String TEST_SITE_3 = TEST_SITE+"3"; + private static final String TEST_SITE_4 = TEST_SITE+"4"; + private static final String TEST_SITE_5 = TEST_SITE+"5"; + private static final String TEST_SITE_6 = TEST_SITE+"6"; + private static final String TEST_SITE_7 = TEST_SITE+"7"; + + private static final String TEST_USER_A = "testUserA"; + private static final String TEST_USER_B = "testUserB"; + private static final String TEST_USER_C = "testUserC"; + private static final String TEST_USER_D = "testUserD"; + private static final String TEST_USER_E = "testUserE"; + private static final String TEST_USER_F = "testUserF"; + @Override public void setUp() throws Exception { siteService = (SiteService) ctx.getBean("SiteService"); + personService = (PersonService) ctx.getBean("PersonService"); feedDAO = (ActivityFeedDAO) ctx.getBean("feedDAO"); transactionHelper = (RetryingTransactionHelper)ctx.getBean("retryingTransactionHelper"); @@ -96,15 +118,15 @@ public class FeedCleanerTest extends TestCase { cleaner.setMaxFeedSize(0); - // insert site feed entries for "testSite1" + // insert site feed entries for TEST_SITE_1 ActivityFeedEntity feedEntry = new ActivityFeedEntity(); feedEntry.setPostDate(new Date(System.currentTimeMillis()-(20*60*1000L))); // 20 mins ago feedEntry.setActivitySummaryFormat("json"); - feedEntry.setSiteNetwork("testSite1"); + feedEntry.setSiteNetwork(TEST_SITE_1); feedEntry.setActivityType("testActivityType"); - feedEntry.setPostUserId("testUserA"); + feedEntry.setPostUserId(TEST_USER_A); feedEntry.setFeedUserId(""); feedEntry.setFeedDate(new Date()); @@ -114,13 +136,13 @@ public class FeedCleanerTest extends TestCase feedEntry.setPostDate(new Date()); // now feedEntry.setActivitySummaryFormat("json"); - feedEntry.setSiteNetwork("testSite1"); + feedEntry.setSiteNetwork(TEST_SITE_1); feedEntry.setActivityType("testActivityType"); - feedEntry.setPostUserId("testUserA"); + feedEntry.setPostUserId(TEST_USER_A); feedEntry.setFeedUserId(""); feedEntry.setFeedDate(new Date()); - // insert user feed entries for "testUserB" + // insert user feed entries for TEST_USER_B feedDAO.insertFeedEntry(feedEntry); @@ -128,10 +150,10 @@ public class FeedCleanerTest extends TestCase feedEntry.setPostDate(new Date(System.currentTimeMillis()-(20*60*1000L))); // 20 mins ago feedEntry.setActivitySummaryFormat("json"); - feedEntry.setSiteNetwork("testSite2"); + feedEntry.setSiteNetwork(TEST_SITE_2); feedEntry.setActivityType("testActivityType"); - feedEntry.setPostUserId("testUserA"); - feedEntry.setFeedUserId("testUserB"); + feedEntry.setPostUserId(TEST_USER_A); + feedEntry.setFeedUserId(TEST_USER_B); feedEntry.setFeedDate(new Date()); feedDAO.insertFeedEntry(feedEntry); @@ -140,30 +162,30 @@ public class FeedCleanerTest extends TestCase feedEntry.setPostDate(new Date()); // now feedEntry.setActivitySummaryFormat("json"); - feedEntry.setSiteNetwork("testSite3"); + feedEntry.setSiteNetwork(TEST_SITE_3); feedEntry.setActivityType("testActivityType"); - feedEntry.setPostUserId("testUserA"); - feedEntry.setFeedUserId("testUserB"); + feedEntry.setPostUserId(TEST_USER_A); + feedEntry.setFeedUserId(TEST_USER_B); feedEntry.setFeedDate(new Date()); feedDAO.insertFeedEntry(feedEntry); - assertEquals(2, feedDAO.selectSiteFeedEntries("testSite1", "json").size()); - assertEquals(2, feedDAO.selectUserFeedEntries("testUserB", "json", null, false, false).size()); + assertEquals(2, feedDAO.selectSiteFeedEntries(TEST_SITE_1, "json", -1).size()); + assertEquals(2, feedDAO.selectUserFeedEntries(TEST_USER_B, "json", null, false, false, -1L, -1).size()); // fire the cleaner cleaner.setMaxAgeMins(10); cleaner.execute(); - assertEquals(1, feedDAO.selectSiteFeedEntries("testSite1", "json").size()); - assertEquals(1, feedDAO.selectUserFeedEntries("testUserB", "json", null, false, false).size()); + assertEquals(1, feedDAO.selectSiteFeedEntries(TEST_SITE_1, "json", -1).size()); + assertEquals(1, feedDAO.selectUserFeedEntries(TEST_USER_B, "json", null, false, false, -1L, -1).size()); } public void testMaxSize() throws Exception { cleaner.setMaxAgeMins(0); - // insert site feed entries for "testSite4" + // insert site feed entries for TEST_SITE_4 for (int i = 0; i < 10; i++) { @@ -171,16 +193,16 @@ public class FeedCleanerTest extends TestCase feedEntry.setPostDate(new Date(System.currentTimeMillis()-(i*60*1000L))); feedEntry.setActivitySummaryFormat("json"); - feedEntry.setSiteNetwork("testSite4"); + feedEntry.setSiteNetwork(TEST_SITE_4); feedEntry.setActivityType("testActivityType"); - feedEntry.setPostUserId("testUserC"); + feedEntry.setPostUserId(TEST_USER_C); feedEntry.setFeedUserId(""); feedEntry.setFeedDate(new Date()); feedDAO.insertFeedEntry(feedEntry); } - // insert user feed entries for user "testUserD" + // insert user feed entries for user TEST_USER_D for (int i = 0; i < 10; i++) { @@ -188,28 +210,28 @@ public class FeedCleanerTest extends TestCase feedEntry.setPostDate(new Date(System.currentTimeMillis()-(i*60*1000L))); feedEntry.setActivitySummaryFormat("json"); - feedEntry.setSiteNetwork("testSite5"); + feedEntry.setSiteNetwork(TEST_SITE_5); feedEntry.setActivityType("testActivityType"); - feedEntry.setPostUserId("testUserA"); - feedEntry.setFeedUserId("testUserD"); + feedEntry.setPostUserId(TEST_USER_A); + feedEntry.setFeedUserId(TEST_USER_D); feedEntry.setFeedDate(new Date()); feedDAO.insertFeedEntry(feedEntry); } - assertEquals(10, feedDAO.selectSiteFeedEntries("testSite4", "json").size()); - assertEquals(10, feedDAO.selectUserFeedEntries("testUserD", "json", null, false, false).size()); + assertEquals(10, feedDAO.selectSiteFeedEntries(TEST_SITE_4, "json", -1).size()); + assertEquals(10, feedDAO.selectUserFeedEntries(TEST_USER_D, "json", null, false, false, -1L, -1).size()); // fire the cleaner cleaner.setMaxFeedSize(2); cleaner.execute(); - assertEquals(2, feedDAO.selectSiteFeedEntries("testSite4", "json").size()); - assertEquals(2, feedDAO.selectUserFeedEntries("testUserD", "json", null, false, false).size()); + assertEquals(2, feedDAO.selectSiteFeedEntries(TEST_SITE_4, "json", -1).size()); + assertEquals(2, feedDAO.selectUserFeedEntries(TEST_USER_D, "json", null, false, false, -1L, -1).size()); Date sameTime = new Date(); - // insert site feed entries for "testSite6" + // insert site feed entries for TEST_SITE_6 for (int i = 0; i < 10; i++) { @@ -217,16 +239,16 @@ public class FeedCleanerTest extends TestCase feedEntry.setPostDate(sameTime); feedEntry.setActivitySummaryFormat("json"); - feedEntry.setSiteNetwork("testSite6"); + feedEntry.setSiteNetwork(TEST_SITE_6); feedEntry.setActivityType("testActivityType"); - feedEntry.setPostUserId("testUserE"); + feedEntry.setPostUserId(TEST_USER_E); feedEntry.setFeedUserId(""); feedEntry.setFeedDate(new Date()); feedDAO.insertFeedEntry(feedEntry); } - // insert user feed entries for user "testUserF" + // insert user feed entries for user TEST_USER_F for (int i = 0; i < 10; i++) { @@ -234,17 +256,17 @@ public class FeedCleanerTest extends TestCase feedEntry.setPostDate(sameTime); feedEntry.setActivitySummaryFormat("json"); - feedEntry.setSiteNetwork("testSite7"); + feedEntry.setSiteNetwork(TEST_SITE_7); feedEntry.setActivityType("testActivityType"); - feedEntry.setPostUserId("testUserA"); - feedEntry.setFeedUserId("testUserF"); + feedEntry.setPostUserId(TEST_USER_A); + feedEntry.setFeedUserId(TEST_USER_F); feedEntry.setFeedDate(new Date()); feedDAO.insertFeedEntry(feedEntry); } - assertEquals(10, feedDAO.selectSiteFeedEntries("testSite6", "json").size()); - assertEquals(10, feedDAO.selectUserFeedEntries("testUserF", "json", null, false, false).size()); + assertEquals(10, feedDAO.selectSiteFeedEntries(TEST_SITE_6, "json", -1).size()); + assertEquals(10, feedDAO.selectUserFeedEntries(TEST_USER_F, "json", null, false, false, -1L, -1).size()); // fire the cleaner cleaner.setMaxFeedSize(2); @@ -252,8 +274,162 @@ public class FeedCleanerTest extends TestCase // note: no effect, since entries at max feed size have same time (eg. to nearest minute) - assertEquals(10, feedDAO.selectSiteFeedEntries("testSite6", "json").size()); - assertEquals(10, feedDAO.selectUserFeedEntries("testUserF", "json", null, false, false).size()); + assertEquals(10, feedDAO.selectSiteFeedEntries(TEST_SITE_6, "json", -1).size()); + assertEquals(10, feedDAO.selectUserFeedEntries(TEST_USER_F, "json", null, false, false, -1L, -1).size()); + } + + public void testSiteDelete() throws Exception + { + cleaner.setMaxAgeMins(100); + + assertEquals(0, feedDAO.selectSiteFeedEntries(TEST_SITE_4, "json", -1).size()); + assertEquals(0, feedDAO.selectUserFeedEntries(TEST_USER_D, "json", null, false, false, -1L, -1).size()); + + int site4FeedCnt = 10; + + // insert site / user feed entries (for TEST_SITE_4 and TEST_USER_D) + for (int i = 0; i < site4FeedCnt; i++) + { + ActivityFeedEntity feedEntry = new ActivityFeedEntity(); + + feedEntry.setPostDate(new Date(System.currentTimeMillis()-(i*60*1000L))); + feedEntry.setActivitySummaryFormat("json"); + feedEntry.setSiteNetwork(TEST_SITE_4); + feedEntry.setActivityType("testActivityType"); + feedEntry.setPostUserId(TEST_USER_C); + feedEntry.setFeedUserId(""); + feedEntry.setFeedDate(new Date()); + + feedDAO.insertFeedEntry(feedEntry); // for TEST_SITE_4 site feed + + feedEntry.setFeedUserId(TEST_USER_D); // for TEST_USER_D user feed + feedEntry.setFeedDate(new Date()); + + feedDAO.insertFeedEntry(feedEntry); + } + + int site5FeedCnt = 5; + + // add some additional user feed entries (for TEST_SITE_5 and TEST_USER_D) + for (int i = 0; i < site5FeedCnt; i++) + { + ActivityFeedEntity feedEntry = new ActivityFeedEntity(); + + feedEntry.setPostDate(new Date(System.currentTimeMillis()-(i*60*1000L))); + feedEntry.setActivitySummaryFormat("json"); + feedEntry.setSiteNetwork(TEST_SITE_5); + feedEntry.setActivityType("testActivityType"); + feedEntry.setPostUserId(TEST_USER_C); + feedEntry.setFeedUserId(TEST_USER_D); + feedEntry.setFeedDate(new Date()); + + feedDAO.insertFeedEntry(feedEntry); + } + + assertEquals(site4FeedCnt, feedDAO.selectSiteFeedEntries(TEST_SITE_4, "json", -1).size()); + assertEquals(site4FeedCnt+site5FeedCnt, feedDAO.selectUserFeedEntries(TEST_USER_D, "json", null, false, false,-1L, -1).size()); + + // delete the site + siteService.deleteSite(TEST_SITE_4); + + assertEquals(0, feedDAO.selectSiteFeedEntries(TEST_SITE_4, "json", -1).size()); + assertEquals(site5FeedCnt, feedDAO.selectUserFeedEntries(TEST_USER_D, "json", null, false, false, -1L, -1).size()); + + siteService.createSite("mypreset", TEST_SITE_4, TEST_SITE_4, TEST_SITE_4, SiteVisibility.PUBLIC); + + assertEquals(0, feedDAO.selectSiteFeedEntries(TEST_SITE_4, "json", -1).size()); + } + + public void testPersonDelete() throws Exception + { + cleaner.setMaxAgeMins(100); + + createPerson(TEST_USER_E); // ignore result + + assertEquals(0, feedDAO.selectSiteFeedEntries(TEST_SITE_6, "json", -1).size()); + assertEquals(0, feedDAO.selectUserFeedEntries(TEST_USER_E, "json", null, false, false, -1L, -1).size()); + + int site6FeedCnt = 10; + + // insert site / user feed entries (for TEST_SITE_6 and TEST_USER_E) + for (int i = 0; i < site6FeedCnt; i++) + { + ActivityFeedEntity feedEntry = new ActivityFeedEntity(); + + feedEntry.setPostDate(new Date(System.currentTimeMillis()-(i*60*1000L))); + feedEntry.setActivitySummaryFormat("json"); + feedEntry.setSiteNetwork(TEST_SITE_6); + feedEntry.setActivityType("testActivityType"); + feedEntry.setPostUserId(TEST_USER_E); + feedEntry.setFeedUserId(""); + feedEntry.setFeedDate(new Date()); + + feedDAO.insertFeedEntry(feedEntry); // for TEST_SITE_6 site feed + + feedEntry.setFeedUserId(TEST_USER_E); // for TEST_USER_E user feed + feedEntry.setFeedDate(new Date()); + + feedDAO.insertFeedEntry(feedEntry); + } + + int site7FeedCnt = 5; + + // insert site / user feed entries (for TEST_SITE_7 and TEST_USER_E) + for (int i = 0; i < site7FeedCnt; i++) + { + ActivityFeedEntity feedEntry = new ActivityFeedEntity(); + + feedEntry.setPostDate(new Date(System.currentTimeMillis()-(i*60*1000L))); + feedEntry.setActivitySummaryFormat("json"); + feedEntry.setSiteNetwork(TEST_SITE_7); + feedEntry.setActivityType("testActivityType"); + feedEntry.setPostUserId(TEST_USER_E); + feedEntry.setFeedUserId(""); + feedEntry.setFeedDate(new Date()); + + feedDAO.insertFeedEntry(feedEntry); // for TEST_SITE_7 site feed + + feedEntry.setFeedUserId(TEST_USER_E); // for TEST_USER_E user feed + feedEntry.setFeedDate(new Date()); + + feedDAO.insertFeedEntry(feedEntry); + } + + assertEquals(site6FeedCnt, feedDAO.selectSiteFeedEntries(TEST_SITE_6, "json", -1).size()); + assertEquals(site7FeedCnt, feedDAO.selectSiteFeedEntries(TEST_SITE_7, "json", -1).size()); + assertEquals(site6FeedCnt+site7FeedCnt, feedDAO.selectUserFeedEntries(TEST_USER_E, "json", null, false, false, -1L, -1).size()); + + // delete the person + personService.deletePerson(TEST_USER_E); + + assertEquals(site6FeedCnt, feedDAO.selectSiteFeedEntries(TEST_SITE_6, "json", -1).size()); + assertEquals(site7FeedCnt, feedDAO.selectSiteFeedEntries(TEST_SITE_7, "json", -1).size()); + + assertEquals(0, feedDAO.selectUserFeedEntries(TEST_USER_E, "json", null, false, false, -1L, -1).size()); + + assertTrue(createPerson(TEST_USER_E)); + + assertEquals(0, feedDAO.selectUserFeedEntries(TEST_USER_E, "json", null, false, false, -1L, -1).size()); + } + + private boolean createPerson(String userName) + { + if (this.personService.personExists(userName) == false) + { + // create person properties + PropertyMap personProps = new PropertyMap(); + personProps.put(ContentModel.PROP_USERNAME, userName); + personProps.put(ContentModel.PROP_FIRSTNAME, userName); + personProps.put(ContentModel.PROP_LASTNAME, userName); + personProps.put(ContentModel.PROP_EMAIL, userName+"@email.com"); + + // create person node for user + this.personService.createPerson(personProps); + + return true; + } + + return false; // already exists } public void testConcurrentAccessAndRemoval() throws Exception @@ -322,9 +498,9 @@ public class FeedCleanerTest extends TestCase feedEntry.setPostDate(new Date(System.currentTimeMillis()-(i*60*1000L))); feedEntry.setActivitySummaryFormat("json"); - feedEntry.setSiteNetwork("testSite4"); + feedEntry.setSiteNetwork(TEST_SITE_4); feedEntry.setActivityType("testActivityType"); - feedEntry.setPostUserId("testUserC"); + feedEntry.setPostUserId(TEST_USER_C); feedEntry.setFeedUserId(""); feedEntry.setFeedDate(new Date()); @@ -350,7 +526,7 @@ public class FeedCleanerTest extends TestCase public Integer execute() throws Throwable { // query some entries - int selectCount = feedDAO.selectSiteFeedEntries("testSite4", "json").size(); + int selectCount = feedDAO.selectSiteFeedEntries(TEST_SITE_4, "json", -1).size(); return selectCount; } }); @@ -385,4 +561,4 @@ public class FeedCleanerTest extends TestCase } } } -} \ No newline at end of file +} diff --git a/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java index aa7888ce43..77869aa073 100644 --- a/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java +++ b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java @@ -139,7 +139,10 @@ public class PostLookup if (activityPosts.size() > 0) { - logger.info("Update: " + activityPosts.size() + " activity posts"); + if (logger.isDebugEnabled()) + { + logger.debug("Update: " + activityPosts.size() + " activity post"+(activityPosts.size() == 1 ? "s" : "")); + } } for (final ActivityPostEntity activityPost : activityPosts) diff --git a/source/java/org/alfresco/repo/admin/RepoAdminService.java b/source/java/org/alfresco/repo/admin/RepoAdminService.java deleted file mode 100644 index cbe3ed1a25..0000000000 --- a/source/java/org/alfresco/repo/admin/RepoAdminService.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2005-2010 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.admin; - -import java.io.InputStream; -import java.util.List; - -import org.alfresco.service.Auditable; -import org.alfresco.service.namespace.QName; - - -/** - * Repository Admin Service interface. - *

- * This interface provides certain repository administrative methods to: - * - * - deploy/undeploy custom content models to/from repository - * - deploy/undeploy custom messages resources to/from repository - * - * Initially, this will support models and messages used by workflow process definitions. - */ - -public interface RepoAdminService -{ - /* Custom models managed in the repository */ - - public List getModels(); - - public void deployModel(InputStream modelStream, String modelFileName); - - public QName undeployModel(String modelFileName); - - public QName activateModel(String modelFileName); - - public QName deactivateModel(String modelFileName); - - /* Custom message/resource bundles managed in the repository */ - - public List getMessageBundles(); - - public String deployMessageBundle(String resourceClasspath); - - public void undeployMessageBundle(String bundleBaseName); - - public void reloadMessageBundle(String bundleBaseName); - -} diff --git a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java index c365af6f85..8578fffab0 100644 --- a/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/admin/RepoAdminServiceImpl.java @@ -35,7 +35,11 @@ import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.dictionary.RepositoryLocation; import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.i18n.MessageServiceImpl; +import org.alfresco.repo.usage.RepoUsageComponent; import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.admin.RepoUsage; +import org.alfresco.service.cmr.admin.RepoUsage.UsageType; +import org.alfresco.service.cmr.admin.RepoUsageStatus; import org.alfresco.service.cmr.dictionary.DictionaryException; import org.alfresco.service.cmr.dictionary.ModelDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -61,13 +65,11 @@ import org.springframework.extensions.surf.util.ParameterCheck; * Repository Admin Service Implementation. *

* @see RepoAdminService interface - * */ - public class RepoAdminServiceImpl implements RepoAdminService { // Logging support - private static Log logger = LogFactory.getLog("org.alfresco.repo.admin.RepoAdminServiceImpl"); + private static Log logger = LogFactory.getLog(RepoAdminServiceImpl.class); // dependencies private DictionaryDAO dictionaryDAO; @@ -76,6 +78,7 @@ public class RepoAdminServiceImpl implements RepoAdminService private ContentService contentService; private NamespaceService namespaceService; private MessageService messageService; + private RepoUsageComponent repoUsageComponent; private RepositoryLocation repoModelsLocation; private RepositoryLocation repoMessagesLocation; @@ -116,8 +119,11 @@ public class RepoAdminServiceImpl implements RepoAdminService this.messageService = messageService; } - - + public void setRepoUsageComponent(RepoUsageComponent repoUsageComponent) + { + this.repoUsageComponent = repoUsageComponent; + } + public void setRepositoryModelsLocation(RepositoryLocation repoModelsLocation) { this.repoModelsLocation = repoModelsLocation; @@ -128,11 +134,6 @@ public class RepoAdminServiceImpl implements RepoAdminService this.repoMessagesLocation = repoMessagesLocation; } - - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.admin.RepoAdminService#getModels() - */ public List getModels() { StoreRef storeRef = repoModelsLocation.getStoreRef(); @@ -198,10 +199,6 @@ public class RepoAdminServiceImpl implements RepoAdminService return modelsInRepo; } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.admin.RepoAdminService#deployModel(java.io.InputStream, java.lang.String) - */ public void deployModel(InputStream modelStream, String modelFileName) { try @@ -294,10 +291,6 @@ public class RepoAdminServiceImpl implements RepoAdminService } } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.admin.RepoAdminService#activateModel(java.lang.String) - */ public QName activateModel(String modelFileName) { try @@ -310,10 +303,6 @@ public class RepoAdminServiceImpl implements RepoAdminService } } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.admin.RepoAdminService#deactivateModel(java.lang.String) - */ public QName deactivateModel(String modelFileName) { try @@ -424,10 +413,6 @@ public class RepoAdminServiceImpl implements RepoAdminService return modelQName; } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.admin.RepoAdminService#undeployModel(java.lang.String) - */ public QName undeployModel(String modelFileName) { // Check that all the passed values are not null @@ -522,10 +507,6 @@ public class RepoAdminServiceImpl implements RepoAdminService return modelQName; } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.admin.RepoAdminService#getMessageBundles() - */ public List getMessageBundles() { StoreRef storeRef = repoMessagesLocation.getStoreRef(); @@ -580,10 +561,6 @@ public class RepoAdminServiceImpl implements RepoAdminService return resourceBundlesInRepo; } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.admin.RepoAdminService#deployMessageBundle(java.lang.String) - */ public String deployMessageBundle(String resourceClasspath) { // Check that all the passed values are not null @@ -744,10 +721,6 @@ public class RepoAdminServiceImpl implements RepoAdminService } } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.admin.RepoAdminService#undeployMessageBundle(java.lang.String) - */ public void undeployMessageBundle(String bundleBaseName) { checkBundleBaseName(bundleBaseName); @@ -792,10 +765,6 @@ public class RepoAdminServiceImpl implements RepoAdminService } } - /* - * (non-Javadoc) - * @see org.alfresco.service.cmr.admin.RepoAdminService#reloadMessageBundle(java.lang.String) - */ public void reloadMessageBundle(String bundleBaseName) { checkBundleBaseName(bundleBaseName); @@ -860,4 +829,28 @@ public class RepoAdminServiceImpl implements RepoAdminService throw new AlfrescoRuntimeException("Message deployment failed - bundle base name '" + bundleBaseName + "' should not contain '.' (period)"); } } + + @Override + public RepoUsage getRestrictions() + { + return repoUsageComponent.getRestrictions(); + } + + @Override + public RepoUsage getUsage() + { + return repoUsageComponent.getUsage(); + } + + @Override + public boolean updateUsage(UsageType usageType) + { + return repoUsageComponent.updateUsage(usageType); + } + + @Override + public RepoUsageStatus getUsageStatus() + { + return repoUsageComponent.getUsageStatus(); + } } diff --git a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java index 6b4a190351..ef47fed96b 100644 --- a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java @@ -32,6 +32,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.Tenant; import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.admin.PatchException; import org.alfresco.service.cmr.repository.NodeService; @@ -92,6 +93,8 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher private PatchService patchService; /** used to ensure a unique transaction per execution */ protected TransactionService transactionService; + /** Use this helper to ensure that patches can execute even on a read-only system */ + protected RetryingTransactionHelper transactionHelper; protected NamespaceService namespaceService; protected NodeService nodeService; protected SearchService searchService; @@ -140,6 +143,8 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; + this.transactionHelper = transactionService.getRetryingTransactionHelper(); + this.transactionHelper.setForceWritable(true); } public void setNamespaceService(NamespaceService namespaceService) @@ -378,6 +383,7 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher checkPropertyNotNull(id, "id"); checkPropertyNotNull(description, "description"); checkPropertyNotNull(transactionService, "transactionService"); + checkPropertyNotNull(transactionHelper, "transactionHelper"); checkPropertyNotNull(namespaceService, "namespaceService"); checkPropertyNotNull(nodeService, "nodeService"); checkPropertyNotNull(searchService, "searchService"); diff --git a/source/java/org/alfresco/repo/admin/patch/PatchExecuter.java b/source/java/org/alfresco/repo/admin/patch/PatchExecuter.java index 05d387ec14..58dc427614 100644 --- a/source/java/org/alfresco/repo/admin/patch/PatchExecuter.java +++ b/source/java/org/alfresco/repo/admin/patch/PatchExecuter.java @@ -22,12 +22,13 @@ import java.util.Date; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; -import org.springframework.extensions.surf.util.I18NUtil; -import org.alfresco.service.transaction.TransactionService; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.extensions.surf.util.I18NUtil; /** * This component is responsible for ensuring that patches are applied @@ -47,7 +48,6 @@ public class PatchExecuter extends AbstractLifecycleBean private static Log logger = LogFactory.getLog(PatchExecuter.class); private PatchService patchService; - private TransactionService transactionService; /** * @param patchService the server that actually executes the patches @@ -57,21 +57,14 @@ public class PatchExecuter extends AbstractLifecycleBean this.patchService = patchService; } - /** - * @param transactionService provides the system read-only state - */ - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - /** * Ensures that all outstanding patches are applied. */ public void applyOutstandingPatches() { - // Avoid read-only systems - if (!patchService.validatePatches() || transactionService.isReadOnly()) + // Apply patches even if we are in read only mode. The system may not work safely otherwise. + + if (!patchService.validatePatches()) { logger.warn(I18NUtil.getMessage(MSG_SYSTEM_READ_ONLY)); return; @@ -123,7 +116,16 @@ public class PatchExecuter extends AbstractLifecycleBean @Override protected void onBootstrap(ApplicationEvent event) { - applyOutstandingPatches(); + RunAsWork runPatches = new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + applyOutstandingPatches(); + return null; + } + }; + AuthenticationUtil.runAs(runPatches, AuthenticationUtil.getSystemUserName()); } @Override @@ -131,5 +133,4 @@ public class PatchExecuter extends AbstractLifecycleBean { // NOOP } - } diff --git a/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java b/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java index 0a16be71bd..b8cca69d8e 100644 --- a/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java +++ b/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java @@ -26,22 +26,21 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.transaction.UserTransaction; - import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.domain.patch.AppliedPatchDAO; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.TransactionServiceImpl; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transaction.TransactionServiceImpl; import org.alfresco.service.cmr.admin.PatchException; import org.alfresco.service.cmr.rule.RuleService; 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.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.I18NUtil; -import org.springframework.transaction.PlatformTransactionManager; /** @@ -100,6 +99,9 @@ public class PatchServiceImpl implements PatchService { patches.add(patch); } + + private final QName vetoName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "PatchServiceImpl"); + public boolean validatePatches() { @@ -117,7 +119,7 @@ public class PatchServiceImpl implements PatchService } if (!success) { - this.transactionService.setAllowWrite(false); + this.transactionService.setAllowWrite(false, vetoName); } return success; } @@ -405,7 +407,8 @@ public class PatchServiceImpl implements PatchService * * @return true: continue, false: do not apply patch */ - private void setup() { + private void setup() + { transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { @Override public Object execute() throws Throwable { @@ -494,7 +497,8 @@ public class PatchServiceImpl implements PatchService } } - private void save() { + private void save() + { if(!savePatch()) { return; @@ -557,7 +561,8 @@ public class PatchServiceImpl implements PatchService }, false, true); } - public AppliedPatch getAppliedPatch() { + public AppliedPatch getAppliedPatch() + { return appliedPatch; } } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java index a3e21f1f06..ecc5d3a030 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/AuthorityMigrationPatch.java @@ -302,7 +302,7 @@ public class AuthorityMigrationPatch extends AbstractPatch // Migrate using 2 threads, 20 authorities per transaction. Log every 100 entries. new BatchProcessor>>( I18NUtil.getMessage(AuthorityMigrationPatch.MSG_PROCESS_NAME), - this.transactionService.getRetryingTransactionHelper(), + transactionHelper, parentAssocs.entrySet(), 2, 20, AuthorityMigrationPatch.this.applicationEventPublisher, diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ContentUrlConverterPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ContentUrlConverterPatch.java index 669fe6510a..1806245671 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/ContentUrlConverterPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/ContentUrlConverterPatch.java @@ -48,6 +48,8 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; import org.alfresco.util.VmShutdownListener; import org.alfresco.util.VmShutdownListener.VmShutdownException; import org.apache.commons.lang.mutable.MutableInt; @@ -60,8 +62,6 @@ import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.extensions.surf.util.I18NUtil; -import org.alfresco.util.Pair; -import org.alfresco.util.PropertyCheck; /** * Component to migrate old-style content URL storage (contentUrl=store://...|mimetype=...) @@ -246,7 +246,7 @@ public class ContentUrlConverterPatch extends AbstractPatch } } }; - return transactionService.getRetryingTransactionHelper().doInTransaction(patchTxn); + return transactionHelper.doInTransaction(patchTxn); } }; String report = AuthenticationUtil.runAs(patchRunAs, AuthenticationUtil.getSystemUserName()); @@ -374,7 +374,7 @@ public class ContentUrlConverterPatch extends AbstractPatch { refreshLock(lockToken, batchSize*100L); - done = transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true); + done = transactionHelper.doInTransaction(callback, false, true); if (done) { break; @@ -434,7 +434,7 @@ public class ContentUrlConverterPatch extends AbstractPatch }; BatchProcessor> batchProcessor = new BatchProcessor>( "ContentUrlConverter.ADM (" + maxId + ")", - transactionService.getRetryingTransactionHelper(), + transactionHelper, batchProcessorWork, threadCount, 1, applicationEventPublisher, null, 1); batchProcessor.process(batchProcessorWorker, true); @@ -481,7 +481,7 @@ public class ContentUrlConverterPatch extends AbstractPatch { refreshLock(lockToken, batchSize*100L); - done = transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true); + done = transactionHelper.doInTransaction(callback, false, true); if (done) { break; @@ -532,7 +532,7 @@ public class ContentUrlConverterPatch extends AbstractPatch }; BatchProcessor batchProcessor = new BatchProcessor( "ContentUrlConverter.AVM (" + maxId + ")", - transactionService.getRetryingTransactionHelper(), + transactionHelper, nodeIds, threadCount, batchSize, applicationEventPublisher, null, 1); batchProcessor.process(batchProcessorWorker, true); @@ -574,7 +574,7 @@ public class ContentUrlConverterPatch extends AbstractPatch return applyUrlLiftingInTxn(lockToken); } }; - return transactionService.getRetryingTransactionHelper().doInTransaction(callback, false, true); + return transactionHelper.doInTransaction(callback, false, true); } private boolean applyUrlLiftingInTxn(final String lockToken) throws Exception diff --git a/source/java/org/alfresco/repo/admin/patch/impl/DmPermissionsPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/DmPermissionsPatch.java index 2c68b1e093..8abfac66c5 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/DmPermissionsPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/DmPermissionsPatch.java @@ -95,6 +95,7 @@ public class DmPermissionsPatch extends AbstractPatch { RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper(); txHelper.setMaxRetries(1); + txHelper.setForceWritable(true); RetryingTransactionCallback callback = new RetryingTransactionCallback() { public Long execute() throws Throwable diff --git a/source/java/org/alfresco/repo/admin/patch/impl/EliminateDuplicatesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/EliminateDuplicatesPatch.java index 19a74ee29b..37f37f9f25 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/EliminateDuplicatesPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/EliminateDuplicatesPatch.java @@ -39,6 +39,8 @@ import org.springframework.orm.ibatis.SqlMapClientTemplate; * of duplicates to make them unique. This is achieved by appending a unique suffix to * duplicate node names. * + * @deprecated superceded by "patch.db-V3.4-AVM-rename-dupes" + * * @author Dmitry Velichkevich */ public class EliminateDuplicatesPatch extends AbstractPatch diff --git a/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesInviteAndNotifyFoldersPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesInviteAndNotifyFoldersPatch.java index 946fd65df4..b3e4e258b8 100755 --- a/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesInviteAndNotifyFoldersPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/EmailTemplatesInviteAndNotifyFoldersPatch.java @@ -59,7 +59,7 @@ public class EmailTemplatesInviteAndNotifyFoldersPatch extends AbstractPatch { private static final String PROPERTY_EMAIL_INVITE_TEMPLATES_FOLDER_NAME = "spaces.invite_templates.email.name"; private static final String PROPERTY_EMAIL_INVITE_TEMPLATES_FOLDER_DESCRIPTION = "spaces.invite_templates.email.description"; - private static final String NOTIFY_TEMPLATE_NAME = "notify_user_email.ftl"; + private static final String SAMPLE_NOTIFY_TEMPLATE_NAME = "notify_user_email.ftl.sample"; private static final String INVITE_TEMPLATE_NAME = "invite_user_email.ftl"; private static final String MSG_EMAIL_INVITE_TEMPLATES_FOLDER_EXISTS = "patch.emailInviteTemplatesFolder.result.exists"; @@ -78,7 +78,7 @@ public class EmailTemplatesInviteAndNotifyFoldersPatch extends AbstractPatch { protected Properties configuration; protected NodeRef emailTemplatesFolderNodeRef; - private String emaiemailTemplatesFolderXPath; + private String emailTemplatesFolderXPath; public void setImporterBootstrap(ImporterBootstrap importerBootstrap) { @@ -148,21 +148,21 @@ public class EmailTemplatesInviteAndNotifyFoldersPatch extends AbstractPatch { sb.append("/").append(companyHomeChildName) .append("/").append(dictionaryChildName) .append("/").append(emailTemplatesChildName); - emaiemailTemplatesFolderXPath = sb.toString(); + emailTemplatesFolderXPath = sb.toString(); // get the email templates node - List nodeRefs = searchService.selectNodes(storeRootNodeRef, emaiemailTemplatesFolderXPath, null, namespaceService, false); + List nodeRefs = searchService.selectNodes(storeRootNodeRef, emailTemplatesFolderXPath, null, namespaceService, false); if (nodeRefs.size() == 0) { throw new PatchException("XPath didn't return any results: \n" + " root: " + storeRootNodeRef + "\n" + - " xpath: " + emaiemailTemplatesFolderXPath); + " xpath: " + emailTemplatesFolderXPath); } else if (nodeRefs.size() > 1) { throw new PatchException("XPath returned too many results: \n" + " root: " + storeRootNodeRef + "\n" + - " xpath: " + emaiemailTemplatesFolderXPath + "\n" + + " xpath: " + emailTemplatesFolderXPath + "\n" + " results: " + nodeRefs); } this.emailTemplatesFolderNodeRef = nodeRefs.get(0); @@ -190,7 +190,7 @@ public class EmailTemplatesInviteAndNotifyFoldersPatch extends AbstractPatch { emailNotifyTemplatesFolderNodeRef = createFolderAndMoveTemplate(PROPERTY_EMAIL_NOTIFY_TEMPLATES_FOLDER_CHILDNAME, PROPERTY_EMAIL_NOTIFY_TEMPLATES_FOLDER_NAME, PROPERTY_EMAIL_NOTIFY_TEMPLATES_FOLDER_DESCRIPTION, - NOTIFY_TEMPLATE_NAME); + SAMPLE_NOTIFY_TEMPLATE_NAME); msg.append(I18NUtil.getMessage(MSG_EMAIL_NOTIFY_TEMPLATES_FOLDER_CREATED, emailNotifyTemplatesFolderNodeRef)); } else @@ -281,7 +281,7 @@ public class EmailTemplatesInviteAndNotifyFoldersPatch extends AbstractPatch { nodeService.addAspect(createdFolderNodeRef, ApplicationModel.ASPECT_UIFACETS, null); //move template - String xpath = emaiemailTemplatesFolderXPath + "/cm:" + templateName; + String xpath = emailTemplatesFolderXPath + "/cm:" + templateName; List templateNodeRefs = searchService.selectNodes(emailTemplatesFolderNodeRef, xpath, null, namespaceService, false); for (NodeRef templateNodeRef : templateNodeRefs) { diff --git a/source/java/org/alfresco/repo/admin/patch/impl/FixAuthoritiesCrcValuesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/FixAuthoritiesCrcValuesPatch.java index 8642d5f8b4..8ddf134ecb 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/FixAuthoritiesCrcValuesPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/FixAuthoritiesCrcValuesPatch.java @@ -156,7 +156,7 @@ public class FixAuthoritiesCrcValuesPatch extends AbstractPatch // get the association types to check BatchProcessor batchProcessor = new BatchProcessor( "FixAuthorityCrcValuesPatch", - transactionService.getRetryingTransactionHelper(), + transactionHelper, mismatchedAuthorities, 2, 20, applicationEventPublisher, diff --git a/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java index ec4b66b751..63efb78f88 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/FixNameCrcValuesPatch.java @@ -254,7 +254,7 @@ public class FixNameCrcValuesPatch extends AbstractPatch // get the association types to check BatchProcessor> batchProcessor = new BatchProcessor>( "FixNameCrcValuesPatch", - transactionService.getRetryingTransactionHelper(), + transactionHelper, workProvider, batchThreads, batchSize, applicationEventPublisher, diff --git a/source/java/org/alfresco/repo/admin/patch/impl/FixUserQNamesPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/FixUserQNamesPatch.java index 6a211686c5..0578708d18 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/FixUserQNamesPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/FixUserQNamesPatch.java @@ -33,7 +33,6 @@ import org.alfresco.repo.batch.BatchProcessor; import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker; import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.repo.importer.ImporterBootstrap; -import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.rule.RuleService; @@ -41,7 +40,6 @@ import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.extensions.surf.util.I18NUtil; @@ -59,7 +57,6 @@ public class FixUserQNamesPatch extends AbstractPatch implements ApplicationEven private QNameDAO qnameDAO; private RuleService ruleService; private ImporterBootstrap userBootstrap; - private ApplicationEventPublisher applicationEventPublisher; public FixUserQNamesPatch() { @@ -87,16 +84,6 @@ public class FixUserQNamesPatch extends AbstractPatch implements ApplicationEven { this.userBootstrap = userBootstrap; } - /* - * (non-Javadoc) - * @see - * org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context - * .ApplicationEventPublisher) - */ - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) - { - this.applicationEventPublisher = applicationEventPublisher; - } @Override protected void checkProperties() @@ -104,17 +91,23 @@ public class FixUserQNamesPatch extends AbstractPatch implements ApplicationEven super.checkProperties(); checkPropertyNotNull(qnameDAO, "qnameDAO"); checkPropertyNotNull(userBootstrap, "userBootstrap"); - checkPropertyNotNull(applicationEventPublisher, "applicationEventPublisher"); } @Override protected String applyInternal() throws Exception { // Get the ChildAssociationRefs - List toProcess = nodeService.getChildAssocs(getUserFolderLocation(), ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_USER, - false); + List toProcess = nodeService.getChildAssocs( + getUserFolderLocation(), + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_USER, + false); BatchProcessor batchProcessor = new BatchProcessor( - "FixUserQNamesPatch", this.transactionService.getRetryingTransactionHelper(), toProcess, 2, 20, + "FixUserQNamesPatch", + transactionHelper, + toProcess, + 2, + 20, this.applicationEventPublisher, logger, 1000); int updated = batchProcessor.process(new BatchProcessWorker() @@ -138,8 +131,9 @@ public class FixUserQNamesPatch extends AbstractPatch implements ApplicationEven public void process(ChildAssociationRef entry) throws Throwable { - QName userQName = QName.createQName(ContentModel.USER_MODEL_URI, (String) nodeService.getProperty(entry - .getChildRef(), ContentModel.PROP_USER_USERNAME)); + QName userQName = QName.createQName( + ContentModel.USER_MODEL_URI, + (String) nodeService.getProperty(entry.getChildRef(), ContentModel.PROP_USER_USERNAME)); // Only a user called "user" will stay in place if (!userQName.equals(ContentModel.TYPE_USER)) diff --git a/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java index b0fdf93a41..a4504be5d9 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/ImapFoldersPatch.java @@ -26,7 +26,6 @@ import java.io.InputStream; import java.util.List; import java.util.Properties; -import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.repo.admin.patch.AbstractPatch; import org.alfresco.repo.importer.ACPImportPackageHandler; import org.alfresco.repo.importer.ImporterBootstrap; @@ -41,6 +40,7 @@ import org.alfresco.service.cmr.view.ImporterService; import org.alfresco.service.cmr.view.Location; import org.alfresco.util.TempFileProvider; import org.springframework.context.MessageSource; +import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.util.FileCopyUtils; /** * Builds folders tree necessary for IMAP functionality and imports email action scripts. @@ -239,7 +239,7 @@ public class ImapFoldersPatch extends AbstractPatch }; - transactionService.getRetryingTransactionHelper().doInTransaction(cb, false, true); + transactionHelper.doInTransaction(cb, false, true); msg = I18NUtil.getMessage(MSG_CREATED); } else diff --git a/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java index ff5ffb35ac..4a50d09b54 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/MigrateVersionStorePatch.java @@ -186,7 +186,7 @@ public class MigrateVersionStorePatch extends AbstractPatch } } }; - return transactionService.getRetryingTransactionHelper().doInTransaction(patchTxn); + return transactionHelper.doInTransaction(patchTxn); } }; String report = AuthenticationUtil.runAs(patchRunAs, AuthenticationUtil.getSystemUserName()); @@ -291,7 +291,7 @@ public class MigrateVersionStorePatch extends AbstractPatch return true; } }; - Boolean doMigration = transactionService.getRetryingTransactionHelper().doInTransaction(preMigrate); + Boolean doMigration = transactionHelper.doInTransaction(preMigrate); if(!doMigration.booleanValue()) { return null; diff --git a/source/java/org/alfresco/repo/admin/patch/impl/PersonUsagePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/PersonUsagePatch.java index 685ef15dc0..4a5e89f735 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/PersonUsagePatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/PersonUsagePatch.java @@ -18,13 +18,8 @@ */ package org.alfresco.repo.admin.patch.impl; -import org.alfresco.model.ContentModel; import org.alfresco.repo.admin.patch.AbstractPatch; import org.alfresco.repo.domain.patch.PatchDAO; -import org.alfresco.repo.domain.patch.PatchDAO.StringHandler; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.I18NUtil; @@ -43,25 +38,12 @@ public class PersonUsagePatch extends AbstractPatch private static final String MSG_SUCCESS2 = "patch.personUsagePatch.result2"; private PatchDAO patchDAO; - private StoreRef personStoreRef; - private TenantService tenantService; public void setPatchDAO(PatchDAO patchDAO) { this.patchDAO = patchDAO; } - public void setPersonStoreUrl(String storeUrl) - { - this.personStoreRef = new StoreRef(storeUrl); - } - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - @Override protected String applyInternal() throws Exception { @@ -86,34 +68,6 @@ public class PersonUsagePatch extends AbstractPatch private int addPersonSizeCurrentProperty() { - // get people (users) with missing 'cm:sizeCurrent' property - - CountQueryCallback userHandler = new CountQueryCallback(); - - patchDAO.getUsersWithoutUsageProp(tenantService.getName(personStoreRef), userHandler); - - return userHandler.getCount(); + return patchDAO.addSizeCurrentProp(); } - - private class CountQueryCallback implements StringHandler - { - private int count; - - public CountQueryCallback() - { - count = 0; - } - - public void handle(String uuid) - { - nodeService.setProperty(new NodeRef(personStoreRef, uuid), ContentModel.PROP_SIZE_CURRENT, null); - - count++; - } - - public int getCount() - { - return count; - } - }; } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/SiteLoadPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/SiteLoadPatch.java new file mode 100644 index 0000000000..d9394a4b23 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/SiteLoadPatch.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.admin.patch.impl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.importer.AVMZipBootstrap; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.service.cmr.view.ImporterBinding.UUID_BINDING; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * A Patch based importer which creates and populates + * a site based on the supplied data + * + * @author Nick Burch + */ +public class SiteLoadPatch extends AbstractPatch +{ + public static final String PROPERTIES_USERS = "users"; + public static final String PROPERTIES_PEOPLE = "people"; + public static final String PROPERTIES_GROUPS = "groups"; + public static final String PROPERTIES_CONTENTS = "contents"; + public static final String PROPERTIES_AVM = "avm"; + + private static final Map DEFAULT_PATHS = new HashMap(); + static { + DEFAULT_PATHS.put(PROPERTIES_AVM, null); + DEFAULT_PATHS.put(PROPERTIES_USERS, "/${alfresco_user_store.system_container.childname}/${alfresco_user_store.user_container.childname}"); + DEFAULT_PATHS.put(PROPERTIES_PEOPLE, "/${system.system_container.childname}/${system.people_container.childname}"); + DEFAULT_PATHS.put(PROPERTIES_GROUPS, null); + DEFAULT_PATHS.put(PROPERTIES_CONTENTS, "/${spaces.company_home.childname}/${spaces.sites.childname}"); + } + + private static final String MSG_SITE_ALREADY_EXISTS = "patch.siteLoadPatch.exists"; + private static final String MSG_NO_BOOTSTRAP_VIEWS_GIVEN = "patch.siteLoadPatch.noBootstrapViews"; + private static final String MSG_SITE_CREATED = "patch.siteLoadPatch.result"; + + // Logger + private static final Log logger = LogFactory.getLog(SiteLoadPatch.class); + + private AuthorityService authorityService; + private SiteService siteService; + + private String siteName; + + private ImporterBootstrap spacesBootstrap; + private ImporterBootstrap usersBootstrap; + private AVMZipBootstrap avmBootstrap; + + private Map bootstrapViews; + + public SiteLoadPatch() + { + // We do need to run in our own transaction + setRequiresTransaction(true); + } + + /** + * Sets the name of the site to be bootstrapped. + * + * @param siteName The short name of the site + */ + public void setSiteName(String siteName) + { + this.siteName = siteName; + } + + public void setSpacesBootstrap(ImporterBootstrap spacesBootstrap) + { + this.spacesBootstrap = spacesBootstrap; + } + public void setUsersBootstrap(ImporterBootstrap usersBootstrap) + { + this.usersBootstrap = usersBootstrap; + } + public void setAvmBootstrap(AVMZipBootstrap avmBootstrap) + { + this.avmBootstrap = avmBootstrap; + } + + /** + * Sets the details of the bootstraps to perform + */ + public void setBootstrapViews(Map bootstrapViews) + { + this.bootstrapViews = bootstrapViews; + } + + /** + * Sets the Site Service to be used for importing into + * + * @param siteService The Site Service + */ + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + /** + * Sets the Authority Service to be used for groups and people + * + * @param authorityService The Authority Service + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + @Override + protected void checkProperties() + { + super.checkProperties(); + PropertyCheck.mandatory(this, "siteService", siteService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "siteName", siteName); + } + + @Override + protected String applyInternal() throws Exception + { + AuthenticationUtil.pushAuthentication(); + try + { + // The site service is funny about permissions, + // so even though we're running as the system we + // still need to identify us as the admin user + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + return applyInternalImpl(); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + } + + /** + * Load the site in.
+ * Site will be loaded as admin user. + */ + private String applyInternalImpl() throws Exception + { + if (bootstrapViews == null || bootstrapViews.size() == 0) + { + if (logger.isDebugEnabled()) + { + logger.debug("No Bootstraps given to import from - bootstrap import ignored"); + } + return I18NUtil.getMessage(MSG_NO_BOOTSTRAP_VIEWS_GIVEN, siteName); + } + + // Is the site already there? + // (Run now as we need DB + Security Context) + if (siteService.getSite(siteName) != null) + { + if (logger.isDebugEnabled()) + { + logger.debug("Site " + siteName + " already exists - bootstrap import ignored"); + } + return I18NUtil.getMessage(MSG_SITE_ALREADY_EXISTS, siteName); + } + + // If we get here, we're good to go! + if(logger.isDebugEnabled()) + { + logger.debug("Performing bootstrap of site " + siteName); + } + + + // Create the site as the admin user + SiteInfo site = siteService.createSite( + siteName, siteName, siteName, + null, SiteVisibility.PUBLIC + ); + + // At this point we can go back to being the system + AuthenticationUtil.setRunAsUser(AuthenticationUtil.getSystemUserName()); + + // Setup the Importer Bootstrap Beans + for(ImporterBootstrap bootstrap : new ImporterBootstrap[] { spacesBootstrap, usersBootstrap }) + { + bootstrap.setAllowWrite(true); + bootstrap.setUseExistingStore(true); + bootstrap.setUuidBinding(UUID_BINDING.REPLACE_EXISTING); + } + + // Normally paths aren't given for the views, so supply + // the defaults where they weren't + for(String type : DEFAULT_PATHS.keySet()) + { + Properties props = bootstrapViews.get(type); + if(props != null && DEFAULT_PATHS.get(type) != null) + { + if(! props.containsKey("path")) + { + props.setProperty("path", DEFAULT_PATHS.get(type)); + } + } + } + + // Load our various bootstraps, in the required order + // for things to come in correctly + + // Load any users requested + if(bootstrapViews.containsKey(PROPERTIES_USERS)) + { + List views = new ArrayList(1); + views.add(bootstrapViews.get(PROPERTIES_USERS)); + usersBootstrap.setBootstrapViews(views); + usersBootstrap.bootstrap(); + } + + // Load any people requested + if(bootstrapViews.containsKey(PROPERTIES_PEOPLE)) + { + List views = new ArrayList(1); + views.add(bootstrapViews.get(PROPERTIES_PEOPLE)); + spacesBootstrap.setBootstrapViews(views); + spacesBootstrap.bootstrap(); + } + + // Put people into groups + if(bootstrapViews.containsKey(PROPERTIES_GROUPS)) + { + try + { + doGroupImport( + bootstrapViews.get(PROPERTIES_GROUPS).getProperty("location") + ); + } + catch(Throwable t) + { + throw new AlfrescoRuntimeException("Bootstrap failed", t); + } + } + + // Load the AVM contents + if(bootstrapViews.containsKey(PROPERTIES_AVM)) + { + avmBootstrap.setLocation( + bootstrapViews.get(PROPERTIES_AVM).getProperty("location") + ); + avmBootstrap.bootstrap(); + } + + // Load the Main (ACP) Contents + if(bootstrapViews.containsKey(PROPERTIES_CONTENTS)) + { + // Clear up the stub content that createSite gave us, first + // apply the temporary aspect though to prevent the node from + // being archived + nodeService.addAspect(site.getNodeRef(), ContentModel.ASPECT_TEMPORARY, null); + nodeService.deleteNode(site.getNodeRef()); + + // Now load in the real content from the ACP + List views = new ArrayList(1); + views.add(bootstrapViews.get(PROPERTIES_CONTENTS)); + spacesBootstrap.setBootstrapViews(views); + spacesBootstrap.bootstrap(); + } + + return I18NUtil.getMessage(MSG_SITE_CREATED, siteName); + } + + /** + * Note - This Could potentially be split out into another Bootstrap class, + * but for now is inline to keep things simple + */ + private void doGroupImport(String location) throws Throwable + { + File groupFile = ImporterBootstrap.getFile(location); + BufferedReader reader = new BufferedReader( + new InputStreamReader(new FileInputStream(groupFile), "UTF-8") + ); + + String line; + while( (line = reader.readLine()) != null ) + { + int splitAt = line.indexOf('='); + if(splitAt == -1) + { + logger.warn("Invalid group line " + line); + continue; + } + + String user = line.substring(0, splitAt); + Set currentGroups = authorityService.getAuthoritiesForUser(user); + + StringTokenizer groups = new StringTokenizer(line.substring(splitAt+1), ","); + while(groups.hasMoreTokens()) + { + String group = groups.nextToken(); + if(! currentGroups.contains(group)) + { + authorityService.addAuthority(group, user); + + if(logger.isDebugEnabled()) + { + logger.debug("Added user " + user + " to group " + group); + } + } + } + } + reader.close(); + } +} diff --git a/source/java/org/alfresco/repo/audit/AuditMethodInterceptor.java b/source/java/org/alfresco/repo/audit/AuditMethodInterceptor.java index 3ff79ccdc4..dfd6c1dc2f 100644 --- a/source/java/org/alfresco/repo/audit/AuditMethodInterceptor.java +++ b/source/java/org/alfresco/repo/audit/AuditMethodInterceptor.java @@ -134,6 +134,12 @@ public class AuditMethodInterceptor implements MethodInterceptor public Object invoke(MethodInvocation mi) throws Throwable { + // Bypass all auditing if the system is in read-only mode + if (!transactionService.getAllowWrite()) + { + return mi.proceed(); + } + // Shortcut if no audit values are required if(!auditComponent.areAuditValuesRequired(AUDIT_PATH_API_ROOT)) { // No auditing or server is read-only @@ -403,7 +409,8 @@ public class AuditMethodInterceptor implements MethodInterceptor // rather than a nested transaction to avoid contention for the same audit table threadPoolExecutor.execute(new Runnable() { - public void run() { + public void run() + { Map auditedData; StringBuilder sb = new StringBuilder(1024); diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index 953ef8454d..18ccf9b1ac 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -551,7 +551,6 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi // invokeBeforeDeleteChildAssociation(oldAssocRef); String dstPath = AVMNodeConverter.ExtendAVMPath(dstParent, dstName); NodeRef newChildRef = AVMNodeConverter.ToNodeRef(-1, dstPath); -// invokeBeforeCreateChildAssociation(newParentRef, newChildRef, assocTypeQName, assocQName); // invokeBeforeUpdateNode(oldParentRef); // invokeBeforeUpdateNode(newParentRef); // Actually perform the rename and return a pseudo diff --git a/source/java/org/alfresco/repo/content/ContentServiceImpl.java b/source/java/org/alfresco/repo/content/ContentServiceImpl.java index 786a409bc4..1937889e35 100644 --- a/source/java/org/alfresco/repo/content/ContentServiceImpl.java +++ b/source/java/org/alfresco/repo/content/ContentServiceImpl.java @@ -21,6 +21,7 @@ package org.alfresco.repo.content; import java.io.Serializable; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -573,6 +574,12 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa return transformerRegistry.getTransformer(sourceMimetype, targetMimetype, options); } + public List getActiveTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + return transformerRegistry.getActiveTransformers(sourceMimetype, targetMimetype, options); + } + + /** * @see org.alfresco.service.cmr.repository.ContentService#getImageTransformer() */ diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java index 4a285f58ae..d15a3364d4 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java @@ -20,7 +20,10 @@ package org.alfresco.repo.content.transform; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.alfresco.service.cmr.repository.TransformationOptions; import org.apache.commons.logging.Log; @@ -70,18 +73,34 @@ public class ContentTransformerRegistry /** * Gets the best transformer possible. This is a combination of the most reliable * and the most performant transformer. - * - * TODO */ public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + // Get the sorted list of transformers + List transformers = getActiveTransformers(sourceMimetype, targetMimetype, options); + + // select the most performant transformer + ContentTransformer bestTransformer = null; + if(transformers.size() > 0) + { + bestTransformer = transformers.get(0); + } + // done + return bestTransformer; + } + + /** + * @since 3.5 + */ + public List getActiveTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options) { // Get the list of transformers List transformers = findTransformers(sourceMimetype, targetMimetype, options); - // select the most performant transformer - long bestTime = -1L; - ContentTransformer bestTransformer = null; - for (ContentTransformer transformer : transformers) + final Map activeTransformers = new HashMap(); + + // identify the performance of all the transformers + for (ContentTransformer transformer : transformers) { // Transformability can be dynamic, i.e. it may have become unusable if (transformer.isTransformable(sourceMimetype, targetMimetype, options) == false) @@ -91,15 +110,23 @@ public class ContentTransformerRegistry } long transformationTime = transformer.getTransformationTime(); - // is it better? - if (bestTransformer == null || transformationTime < bestTime) - { - bestTransformer = transformer; - bestTime = transformationTime; - } + activeTransformers.put(transformer, transformationTime); } - // done - return bestTransformer; + + // sort by performance (quicker is "better") + List sorted = new ArrayList(activeTransformers.keySet()); + Collections.sort(sorted, new Comparator() { + + @Override + public int compare(ContentTransformer a, ContentTransformer b) + { + return activeTransformers.get(a).compareTo(activeTransformers.get(b)); + } + + }); + + // All done + return sorted; } /** diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java index 27cccff861..f989ef3c53 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java @@ -19,6 +19,7 @@ package org.alfresco.repo.content.transform; import java.util.Collections; +import java.util.List; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.filestore.FileContentReader; @@ -78,10 +79,10 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe new DummyTransformer(mimetypeService, dummyRegistry, B, C, 10L); // create some dummy transformers for speed tests new DummyTransformer(mimetypeService, dummyRegistry, A, D, 20L); - new DummyTransformer(mimetypeService, dummyRegistry, A, D, 20L); + new DummyTransformer(mimetypeService, dummyRegistry, A, D, 30L); new DummyTransformer(mimetypeService, dummyRegistry, A, D, 10L); // the fast one - new DummyTransformer(mimetypeService, dummyRegistry, A, D, 20L); - new DummyTransformer(mimetypeService, dummyRegistry, A, D, 20L); + new DummyTransformer(mimetypeService, dummyRegistry, A, D, 25L); + new DummyTransformer(mimetypeService, dummyRegistry, A, D, 25L); } /** @@ -135,6 +136,25 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe assertTrue("Incorrect reliability", transformer1.isTransformable(A, D, OPTIONS)); assertFalse("Incorrect reliability", transformer1.isTransformable(D, A, OPTIONS)); assertEquals("Incorrect transformation time", 10L, transformer1.getTransformationTime()); + + // A -> D has 10, 20, 25, 25, 30 + List activeTransformers = dummyRegistry.getActiveTransformers(A, D, OPTIONS); + assertEquals("Not all found", 5, activeTransformers.size()); + assertEquals("Incorrect order", 10L, activeTransformers.get(0).getTransformationTime()); + assertEquals("Incorrect order", 20L, activeTransformers.get(1).getTransformationTime()); + assertEquals("Incorrect order", 25L, activeTransformers.get(2).getTransformationTime()); + assertEquals("Incorrect order", 25L, activeTransformers.get(3).getTransformationTime()); + assertEquals("Incorrect order", 30L, activeTransformers.get(4).getTransformationTime()); + + // Disable two of them, and re-test + ((DummyTransformer)activeTransformers.get(2)).disable(); + ((DummyTransformer)activeTransformers.get(4)).disable(); + + activeTransformers = dummyRegistry.getActiveTransformers(A, D, OPTIONS); + assertEquals("Not all found", 3, activeTransformers.size()); + assertEquals("Incorrect order", 10L, activeTransformers.get(0).getTransformationTime()); + assertEquals("Incorrect order", 20L, activeTransformers.get(1).getTransformationTime()); + assertEquals("Incorrect order", 25L, activeTransformers.get(2).getTransformationTime()); } public void testScoredRetrieval() throws Exception @@ -188,6 +208,7 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe private String sourceMimetype; private String targetMimetype; private long transformationTime; + private boolean disable = false; public DummyTransformer( MimetypeService mimetypeService, @@ -203,9 +224,23 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe // register register(); } + + protected void enable() + { + disable = false; + } + + protected void disable() + { + disable = true; + } public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) { + if (disable) { + return false; + } + if (this.sourceMimetype.equals(sourceMimetype) && this.targetMimetype.equals(targetMimetype)) { diff --git a/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java b/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java index b8b3af7cd7..21d06e91f2 100644 --- a/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -36,6 +36,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.encryption.StandardDecryptionMaterial; /** * Makes use of the {@link http://www.pdfbox.org/ PDFBox} library to @@ -45,12 +46,17 @@ import org.apache.pdfbox.pdmodel.PDPage; */ public class PdfBoxPdfToImageContentTransformer extends AbstractContentTransformer2 { + /** + * The PDF spec allows for a default user password of the empty string. + * See PDF Specification section 3.5 "Password Algorithms", specifically algorithms 3.6 + */ + private static final String PDF_DEFAULT_PASSWORD = ""; private static Log logger = LogFactory.getLog(PdfBoxPdfToImageContentTransformer.class); public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) { - // only support PDF -> PNG - return (MimetypeMap.MIMETYPE_PDF.equals(sourceMimetype) == true && + // only support PDF -> PNG + return (MimetypeMap.MIMETYPE_PDF.equals(sourceMimetype) == true && MimetypeMap.MIMETYPE_IMAGE_PNG.equals(targetMimetype) == true); } @@ -70,21 +76,31 @@ public class PdfBoxPdfToImageContentTransformer extends AbstractContentTransform if (document.isEncrypted()) { - String msg = "PDF document is encrypted."; - if (logger.isInfoEnabled()) + // Encrypted means password-protected, but PDF allows for two passwords: "owner" and "user". + // A "user" of a document should be able to read (but not modify) the content. + // + // We'll attempt to open the document using the common PDF default password. + document.openProtection(new StandardDecryptionMaterial(PDF_DEFAULT_PASSWORD)); + } + boolean canExtractContent = document.getCurrentAccessPermission().canExtractContent(); + if (!canExtractContent) + { + + String msg = "PDF document's intrinsic permissions forbid content extraction."; + if (logger.isDebugEnabled()) { - logger.info(msg); + logger.debug(msg); } throw new AlfrescoRuntimeException(msg); } - final int resolution = 16; //TODO A rather arbitrary number for resolution (DPI) here. + final int resolution = 16; // A rather arbitrary number for resolution (DPI) here. List pages = document.getDocumentCatalog().getAllPages(); PDPage page = (PDPage)pages.get(0); BufferedImage img = page.convertToImage(BufferedImage.TYPE_INT_ARGB, resolution); - File outputFile = TempFileProvider.createTempFile("pdfToImageOutput", ".png"); + File outputFile = TempFileProvider.createTempFile(this.getClass().getSimpleName(), ".png"); ImageIO.write(img, "png", outputFile); writer.putContent(outputFile); diff --git a/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformerTest.java new file mode 100644 index 0000000000..803846ffd5 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformerTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.repo.content.transform; + +import java.io.File; + +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.repo.content.filestore.FileContentWriter; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.alfresco.util.TempFileProvider; + +/** + * Tests for {@link PdfBoxPdfToImageContentTransformer}. + * + * @author Neil Mc Erlean + * @since 3.4.2. + */ +public class PdfBoxPdfToImageContentTransformerTest extends AbstractContentTransformerTest +{ + private ContentTransformer transformer; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + transformer = new PdfBoxPdfToImageContentTransformer(); + ((ContentTransformerHelper)transformer).setMimetypeService(mimetypeService); + } + + /** + * @return Returns the same transformer regardless - it is allowed + */ + protected ContentTransformer getTransformer(String sourceMimetype, String targetMimetype) + { + return transformer; + } + + public void testIsTransformable() throws Exception + { + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_IMAGE_PNG, new TransformationOptions())); + } + + /** + * This test method checks that the PDFBox-based transformer is able to extract text content from a secured PDF file. + * See ALF-6650. + * + * @since 3.4.2 + */ + public void testExtractContentFromSecuredPdf() throws Exception + { + File securePdfFile = loadNamedQuickTestFile("quick-secured.pdf"); + assertNotNull("test file was null.", securePdfFile); + + ContentReader reader = new FileContentReader(securePdfFile); + reader.setMimetype(MimetypeMap.MIMETYPE_PDF); + reader.setEncoding("UTF-8"); + + ContentWriter writer = new FileContentWriter(TempFileProvider.createTempFile(this.getClass().getSimpleName() + System.currentTimeMillis(), "txt")); + writer.setMimetype(MimetypeMap.MIMETYPE_IMAGE_PNG); + writer.setEncoding("UTF-8"); + + transformer.transform(reader, writer); + + // get a reader onto the transformed content and check - although the real test here is that exceptions weren't thrown during transformation. + ContentReader checkReader = writer.getReader(); + checkReader.setMimetype(MimetypeMap.MIMETYPE_IMAGE_PNG); + assertTrue("PNG output was empty", checkReader.getContentData().getSize() != 0l); + } +} diff --git a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java index f6a56dccb7..0f31befff1 100644 --- a/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java +++ b/source/java/org/alfresco/repo/copy/CopyServiceImplTest.java @@ -377,118 +377,84 @@ public class CopyServiceImplTest extends BaseSpringTest QName.createQName("{test}aclCopyOne")); assertEquals(3, permissionService.getAllSetPermissions(copy).size()); + + // Admin - - AuthenticationUtil.runAs(new RunAsWork() - { + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); - @Override - public Void doWork() throws Exception - { - NodeRef copy = copyService.copy( - sourceNodeRef, - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}aclCopyTwo")); - - assertEquals(3, permissionService.getAllSetPermissions(copy).size()); - return null; - } - }, AuthenticationUtil.getAdminUserName()); - - AuthenticationUtil.runAs(new RunAsWork() - { + copy = copyService.copy( + sourceNodeRef, + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}aclCopyTwo")); - @Override - public Void doWork() throws Exception - { - NodeRef copy = copyService.copy( - sourceNodeRef, - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}aclCopyThree")); - - assertEquals(2, permissionService.getAllSetPermissions(copy).size()); - return null; - } - }, AuthenticationUtil.getGuestUserName()); + assertEquals(3, permissionService.getAllSetPermissions(copy).size()); + + // guest + + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); + + copy = copyService.copy( + sourceNodeRef, + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}aclCopyThree")); + assertEquals(2, permissionService.getAllSetPermissions(copy).size()); + + // guest with read permissions - write from ownership + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); permissionService.setPermission(sourceNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.READ_PERMISSIONS, true); - - AuthenticationUtil.runAs(new RunAsWork() - { + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); - @Override - public Void doWork() throws Exception - { - NodeRef copy = copyService.copy( - sourceNodeRef, - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}aclCopyFour")); - - assertEquals(2, permissionService.getAllSetPermissions(copy).size()); - return null; - } - }, AuthenticationUtil.getGuestUserName()); - + copy = copyService.copy( + sourceNodeRef, + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}aclCopyFour")); + + assertEquals(4, permissionService.getAllSetPermissions(copy).size()); + + // guest with read and write + + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); permissionService.setPermission(rootNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.CHANGE_PERMISSIONS, true); - - AuthenticationUtil.runAs(new RunAsWork() - { + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); - @Override - public Void doWork() throws Exception - { - NodeRef copy = copyService.copy( - sourceNodeRef, - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}aclCopyFour")); - - assertEquals(5, permissionService.getAllSetPermissions(copy).size()); - return null; - } - }, AuthenticationUtil.getGuestUserName()); - + copy = copyService.copy( + sourceNodeRef, + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}aclCopyFour")); + + assertEquals(5, permissionService.getAllSetPermissions(copy).size()); + // guest with write but not read + + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); permissionService.setPermission(sourceNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.READ_PERMISSIONS, false); - - AuthenticationUtil.runAs(new RunAsWork() - { + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); - @Override - public Void doWork() throws Exception - { - NodeRef copy = copyService.copy( - sourceNodeRef, - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}aclCopyFour")); - - assertEquals(3, permissionService.getAllSetPermissions(copy).size()); - return null; - } - }, AuthenticationUtil.getGuestUserName()); - + copy = copyService.copy( + sourceNodeRef, + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}aclCopyFour")); + + assertEquals(3, permissionService.getAllSetPermissions(copy).size()); + + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); permissionService.deletePermission(sourceNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.READ_PERMISSIONS); - - AuthenticationUtil.runAs(new RunAsWork() - { + this.authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName()); + + copy = copyService.copy( + sourceNodeRef, + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName("{test}aclCopyFour")); + + assertEquals(3, permissionService.getAllSetPermissions(copy).size()); - @Override - public Void doWork() throws Exception - { - NodeRef copy = copyService.copy( - sourceNodeRef, - rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName("{test}aclCopyFour")); - - assertEquals(3, permissionService.getAllSetPermissions(copy).size()); - return null; - } - }, AuthenticationUtil.getGuestUserName()); } diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorDAO.java b/source/java/org/alfresco/repo/descriptor/DescriptorDAO.java index b854d58f3f..7356e32010 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorDAO.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorDAO.java @@ -18,6 +18,7 @@ */ package org.alfresco.repo.descriptor; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; import org.alfresco.service.descriptor.Descriptor; /** @@ -42,7 +43,7 @@ public interface DescriptorDAO * the current server descriptor * @return the descriptor */ - public Descriptor updateDescriptor(Descriptor serverDescriptor); + public Descriptor updateDescriptor(Descriptor serverDescriptor, LicenseMode licenseMode); /** * Gets the license key. diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java index 79c2499c67..f1091ea499 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java @@ -23,54 +23,53 @@ import java.lang.reflect.Constructor; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.usage.RepoUsageComponent; +import org.alfresco.service.cmr.admin.RepoUsage; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.license.LicenseDescriptor; import org.alfresco.service.license.LicenseException; import org.alfresco.service.license.LicenseService; +import org.alfresco.service.license.LicenseService.LicenseChangeHandler; import org.alfresco.service.transaction.TransactionService; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; import org.alfresco.util.VersionNumber; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; /** * Implementation of Descriptor Service. * * @author David Caruana */ -public class DescriptorServiceImpl extends AbstractLifecycleBean implements DescriptorService, InitializingBean +public class DescriptorServiceImpl extends AbstractLifecycleBean + implements DescriptorService, InitializingBean, LicenseChangeHandler { - /** The server descriptor DAO. */ private DescriptorDAO serverDescriptorDAO; - - /** The current repo descriptor DAO. */ private DescriptorDAO currentRepoDescriptorDAO; - - /** The installed repo descriptor DAO. */ private DescriptorDAO installedRepoDescriptorDAO; - - /** The transaction service. */ + private TransactionService transactionService; - - /** The license service. */ private LicenseService licenseService; - - /** The heart beat service. */ + private RepoUsageComponent repoUsageComponent; @SuppressWarnings("unused") private Object heartBeat; - - /** The server descriptor. */ + + /** + * The version of the software + */ private Descriptor serverDescriptor; - - /** The current repo descriptor. */ private Descriptor currentRepoDescriptor; - - /** The installed repo descriptor. */ private Descriptor installedRepoDescriptor; + + private static final Log logger = LogFactory.getLog(DescriptorServiceImpl.class); /** * Sets the server descriptor DAO. @@ -116,129 +115,154 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc this.transactionService = transactionService; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.DescriptorService#getDescriptor() - */ - public Descriptor getServerDescriptor() + public void setRepoUsageComponent(RepoUsageComponent repoUsageComponent) + { + this.repoUsageComponent = repoUsageComponent; + } + + @Override + public synchronized Descriptor getServerDescriptor() { return this.serverDescriptor; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.DescriptorService#getCurrentRepositoryDescriptor() - */ - public Descriptor getCurrentRepositoryDescriptor() + @Override + public synchronized Descriptor getCurrentRepositoryDescriptor() { return this.currentRepoDescriptor; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.DescriptorService#getRepositoryDescriptor() - */ - public Descriptor getInstalledRepositoryDescriptor() + @Override + public synchronized Descriptor getInstalledRepositoryDescriptor() { return this.installedRepoDescriptor; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.DescriptorService#getLicenseDescriptor() - */ - public LicenseDescriptor getLicenseDescriptor() + @Override + public synchronized LicenseDescriptor getLicenseDescriptor() { return this.licenseService == null ? null : this.licenseService.getLicense(); } - /* - * (non-Javadoc) - * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.ApplicationEvent) + /** + * Load license management method. + * + * Return Message to user to say what happened. + */ + @Override + public String loadLicense() + { + String result = AuthenticationUtil.runAs(new RunAsWork() + { + public String doWork() throws Exception + { + RetryingTransactionHelper helper = transactionService.getRetryingTransactionHelper(); + helper.setForceWritable(true); + + return helper.doInTransaction(new RetryingTransactionCallback() + { + public String execute() + { + return licenseService.loadLicense(); + } + }); + } + }, AuthenticationUtil.getSystemUserName()); + + if (logger.isDebugEnabled()) + { + logger.debug("Load license call returning: " + result); + } + return result; + } + + /** + * On bootstrap load the special services for LicenseComponent and HeartBeat + * + * Also set installedRepoDescriptor and update current */ @Override protected void onBootstrap(ApplicationEvent event) { - AuthenticationUtil.runAs(new RunAsWork() + AuthenticationUtil.RunAsWork bootstrapWork = new RunAsWork() { - public Object doWork() throws Exception + @Override + public Void doWork() throws Exception { - final boolean initialiseHeartBeat; - - // Initialize license service (if installed) - DescriptorServiceImpl.this.licenseService = DescriptorServiceImpl.this.transactionService - .getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback() - { - public LicenseService execute() - { - return (LicenseService) constructSpecialService("org.alfresco.enterprise.license.LicenseComponent"); - } - }, DescriptorServiceImpl.this.transactionService.isReadOnly(), false); - if (DescriptorServiceImpl.this.licenseService == null) - { - DescriptorServiceImpl.this.licenseService = new NOOPLicenseService(); - initialiseHeartBeat = true; - } - else - { - initialiseHeartBeat = false; - } - - // Make the license service available through the application context as a singleton for other beans - // that need it (e.g. the HeartBeat). - ApplicationContext applicationContext = getApplicationContext(); - if (applicationContext instanceof ConfigurableApplicationContext) - { - ((ConfigurableApplicationContext) applicationContext).getBeanFactory().registerSingleton( - "licenseService", DescriptorServiceImpl.this.licenseService); - } - - DescriptorServiceImpl.this.installedRepoDescriptor = DescriptorServiceImpl.this.transactionService - .getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() - { - public Descriptor execute() throws ClassNotFoundException - { - - // verify license, but only if license component is installed - try - { - DescriptorServiceImpl.this.licenseService.verifyLicense(); - LicenseDescriptor l = DescriptorServiceImpl.this.licenseService.getLicense(); - // Initialize the heartbeat unless it is disabled by the license - if (initialiseHeartBeat || l == null || !l.isHeartBeatDisabled()) - { - DescriptorServiceImpl.this.heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat"); - } - } - catch (LicenseException e) - { - // Initialize heart beat anyway - DescriptorServiceImpl.this.heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat"); - throw e; - } - - // persist the server descriptor values - DescriptorServiceImpl.this.currentRepoDescriptor = DescriptorServiceImpl.this.currentRepoDescriptorDAO - .updateDescriptor(DescriptorServiceImpl.this.serverDescriptor); - - // create the installed descriptor - Descriptor installed = DescriptorServiceImpl.this.installedRepoDescriptorDAO - .getDescriptor(); - return installed == null ? new UnknownDescriptor() : installed; - } - }, DescriptorServiceImpl.this.transactionService.isReadOnly(), false); + bootstrap(); return null; } - }, AuthenticationUtil.getSystemUserName()); - + }; + AuthenticationUtil.runAs(bootstrapWork, AuthenticationUtil.getSystemUserName()); + // Broadcast that the descriptor service is now available ((ApplicationContext) event.getSource()).publishEvent(new DescriptorServiceAvailableEvent(this)); } + + private synchronized void bootstrap() + { + logger.debug("onBootstrap"); + + // We force write mode + RetryingTransactionHelper helper = transactionService.getRetryingTransactionHelper(); + helper.setForceWritable(true); + + // create the initial installed descriptor + Descriptor installed = installedRepoDescriptorDAO.getDescriptor(); + if(installed != null) + { + installedRepoDescriptor = installed; + } + else + { + installedRepoDescriptor = new UnknownDescriptor(); + } + + /* + * Initialize license service if on classpath. + * If no class exists a dummy license service is used. + * Make the LicenseService available in the context. + */ + licenseService = (LicenseService) constructSpecialService("org.alfresco.enterprise.license.LicenseComponent"); + if (licenseService == null) + { + // No license server code - install a dummy license service instead + licenseService = new NOOPLicenseService(); + } + ApplicationContext applicationContext = getApplicationContext(); + if (applicationContext instanceof ConfigurableApplicationContext) + { + ((ConfigurableApplicationContext) applicationContext).getBeanFactory().registerSingleton( + "licenseService", licenseService); + } + + // Load heart-beat special service (even if disabled at the moment) + heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat"); + + // Now listen for future license changes + licenseService.registerOnLicenseChange(this); + + // load the license + RetryingTransactionCallback loadLicenseCallback = new RetryingTransactionCallback() + { + public Void execute() + { + try + { + // Verify license has side effect of loading any new licenses + licenseService.verifyLicense(); + return null; + } + catch (LicenseException e) + { + // Swallow Licence Exception Here + // Don't log error: It'll be reported later and the logging fails + return null; + } + } + }; + helper.doInTransaction(loadLicenseCallback, false, false); + } - /* - * (non-Javadoc) - * @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.context.ApplicationEvent) - */ @Override protected void onShutdown(ApplicationEvent event) { @@ -301,41 +325,57 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc */ private class NOOPLicenseService implements LicenseService { - /* - * (non-Javadoc) - * @see org.alfresco.service.license.LicenseService#verifyLicense(org.alfresco.service.descriptor.Descriptor, - * org.alfresco.service.descriptor.DescriptorDAO) + /** + * Ensures that a repository Descriptor is available. + * This is done here to prevent flip-flopping on the license mode + * during startup i.e. the Enterprise mode will be set once when + * the real license is validated. */ public void verifyLicense() throws LicenseException { + if (currentRepoDescriptor == null) + { + currentRepoDescriptor = currentRepoDescriptorDAO.updateDescriptor( + serverDescriptor, + LicenseMode.UNKNOWN); + } } - /* - * (non-Javadoc) - * @see org.alfresco.service.license.LicenseService#isLicenseValid() + /** + * @return true always */ public boolean isLicenseValid() { return true; } - /* - * (non-Javadoc) - * @see org.alfresco.service.license.LicenseService#getLicense() + /** + * @return null always */ public LicenseDescriptor getLicense() throws LicenseException { return null; } - /* - * (non-Javadoc) - * @see org.alfresco.service.license.LicenseService#shutdown() + /** + * NO NOP */ public void shutdown() { } + @Override + public void registerOnLicenseChange(LicenseChangeHandler callback) + { + + } + + @Override + public String loadLicense() + { + return "Done"; + } + } /** @@ -345,122 +385,118 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc */ private class UnknownDescriptor implements Descriptor { - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getId() + /** + * @return Unknown always */ public String getId() { return "Unknown"; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getName() + /** + * @return Unknown always */ public String getName() { return "Unknown"; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionMajor() + /** + * @return Unknown always */ public String getVersionMajor() { return "Unknown"; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionMinor() + /** + * @return Unknown always */ public String getVersionMinor() { return "Unknown"; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionRevision() + /** + * @return Unknown always */ public String getVersionRevision() { return "Unknown"; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionLabel() + /** + * @return Unknown always */ public String getVersionLabel() { return "Unknown"; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionBuild() + /** + * @return Unknown always */ public String getVersionBuild() { return "Unknown"; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionNumber() + /** + * @return 1.0.0 always */ public VersionNumber getVersionNumber() { return new VersionNumber("1.0.0"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersion() + /** + * @return Unknown always */ public String getVersion() { return "Unknown (pre 1.0.0 RC2)"; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getEdition() + /** + * @return Unknown always */ public String getEdition() { return "Unknown"; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getSchema() + /** + * @return 0 always */ public int getSchema() { return 0; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getDescriptorKeys() + /** + * @return Empty String[] always */ public String[] getDescriptorKeys() { return new String[0]; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getDescriptor(java.lang.String) + /** + * @return null always */ public String getDescriptor(String key) { return null; } + + /** + * @return Unknown always + */ + @Override + public LicenseMode getLicenseMode() + { + return LicenseMode.UNKNOWN; + } } /** @@ -477,10 +513,6 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc /** The number as a string. */ private String strVersion = null; - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionNumber() - */ public VersionNumber getVersionNumber() { if (this.versionNumber == null) @@ -496,10 +528,6 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc return this.versionNumber; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersion() - */ public String getVersion() { if (this.strVersion == null) @@ -579,5 +607,66 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc } } } + + @Override + public synchronized void onLicenseChange(final LicenseDescriptor licenseDescriptor) + { + logger.debug("Received changed license descriptor: " + licenseDescriptor); + + RetryingTransactionCallback updateLicenseCallback = new RetryingTransactionCallback() + { + public RepoUsage execute() + { + // Configure the license restrictions + RepoUsage.LicenseMode newMode = licenseDescriptor.getLicenseMode(); + Long expiryTime = licenseDescriptor.getValidUntil() == null ? null : licenseDescriptor.getValidUntil().getTime(); + RepoUsage restrictions = new RepoUsage( + System.currentTimeMillis(), + licenseDescriptor.getMaxUsers(), + licenseDescriptor.getMaxDocs(), + newMode, + expiryTime, + false); + repoUsageComponent.setRestrictions(restrictions); + + // persist the server descriptor values in the current repository descriptor + if (currentRepoDescriptor == null || newMode != currentRepoDescriptor.getLicenseMode()) + { + if (logger.isDebugEnabled()) + { + logger.debug("Changing license mode in current repo descriptor to: " + newMode); + } + currentRepoDescriptor = currentRepoDescriptorDAO.updateDescriptor( + serverDescriptor, + newMode); + } + if (logger.isDebugEnabled()) + { + logger.debug("License restrictions updated: " + restrictions); + } + return null; + } + }; + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + txnHelper.setForceWritable(true); + txnHelper.doInTransaction(updateLicenseCallback); + } + /** + * {@inheritDoc} + *

+ * Restrictions are not changed; previous restrictions remain in place. + */ + @Override + public synchronized void onLicenseFail() + { + // Current restrictions remain in place + // Make sure that the repo descriptor is updated the first time + if (currentRepoDescriptor == null) + { + currentRepoDescriptor = currentRepoDescriptorDAO.updateDescriptor( + serverDescriptor, + LicenseMode.UNKNOWN); + } + } } diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorServiceTest.java b/source/java/org/alfresco/repo/descriptor/DescriptorServiceTest.java index fb7b802196..86fe244345 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorServiceTest.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorServiceTest.java @@ -49,9 +49,6 @@ public class DescriptorServiceTest extends BaseSpringTest this.authenticationComponent = (AuthenticationComponent)this.applicationContext.getBean("authenticationComponent"); this.authenticationComponent.setSystemUserAsCurrentUser(); - - - System.out.println(NodeStoreInspector.dumpNodeStore(nodeService, storeRef)); } @@ -64,6 +61,9 @@ public class DescriptorServiceTest extends BaseSpringTest } + /** + * Test server decriptor + */ public void testServerDescriptor() { ServiceRegistry registry = (ServiceRegistry)applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); @@ -75,6 +75,9 @@ public class DescriptorServiceTest extends BaseSpringTest String revision = serverDescriptor.getVersionRevision(); String label = serverDescriptor.getVersionLabel(); String build = serverDescriptor.getVersionBuild(); + String edition = serverDescriptor.getEdition(); + String id = serverDescriptor.getId(); + String version = major + "." + minor + "." + revision; version = buildVersionString(version, label, build); @@ -82,9 +85,77 @@ public class DescriptorServiceTest extends BaseSpringTest int schemaVersion = serverDescriptor.getSchema(); assertTrue("Server schema version must be greater than 0", schemaVersion > 0); + + assertNotNull("edition is null", edition); + assertEquals("id ", id, "Unknown"); + } + + /** + * Test current repository descriptor + */ + public void testCurrentRepositoryDescriptor() + { + ServiceRegistry registry = (ServiceRegistry)applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + DescriptorService descriptorService = registry.getDescriptorService(); + Descriptor repoDescriptor = descriptorService.getCurrentRepositoryDescriptor(); + + String major = repoDescriptor.getVersionMajor(); + String minor = repoDescriptor.getVersionMinor(); + String revision = repoDescriptor.getVersionRevision(); + String label = repoDescriptor.getVersionLabel(); + String build = repoDescriptor.getVersionBuild(); + String id = repoDescriptor.getId(); + + String version = major + "." + minor + "." + revision; + version = buildVersionString(version, label, build); + + assertEquals(version, repoDescriptor.getVersion()); + + assertNotNull("repository id is null", id); + assertNotNull("major is null", major); + assertNotNull("minor is null", minor); + assertNotNull("revision is null", revision); + + int schemaVersion = repoDescriptor.getSchema(); + assertTrue("Repository schema version must be greater than -1", schemaVersion > -1); + } + + public void testCompareDescriptors() + { + ServiceRegistry registry = (ServiceRegistry)applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + DescriptorService descriptorService = registry.getDescriptorService(); + Descriptor serverDescriptor = descriptorService.getServerDescriptor(); + Descriptor repoDescriptor = descriptorService.getCurrentRepositoryDescriptor(); + + String major1 = repoDescriptor.getVersionMajor(); + String minor1 = repoDescriptor.getVersionMinor(); + String revision1 = repoDescriptor.getVersionRevision(); + String label1 = repoDescriptor.getVersionLabel(); + String build1 = repoDescriptor.getVersionBuild(); + String id1 = repoDescriptor.getId(); + + String major2 = serverDescriptor.getVersionMajor(); + String minor2 = serverDescriptor.getVersionMinor(); + String revision2 = serverDescriptor.getVersionRevision(); + String label2 = serverDescriptor.getVersionLabel(); + String build2 = serverDescriptor.getVersionBuild(); + String id2 = serverDescriptor.getId(); + + assertEquals("major version different", major1, major2); + assertEquals("minor version different", minor1, minor2); + assertEquals("revision version different", revision1, revision2); + assertEquals("label version different", label1, label2); + assertEquals("build version different", build1, build2); + + } - public void testRepositoryDescriptor() + // TODO : missing getLicenceDescriptor + + /** + * Test installed repository descriptor + */ + public void testInstalledRepositoryDescriptor() { ServiceRegistry registry = (ServiceRegistry)applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); DescriptorService descriptorService = registry.getDescriptorService(); diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java b/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java index 8db7d6ce05..cf6bede218 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorStartupLog.java @@ -20,16 +20,19 @@ package org.alfresco.repo.descriptor; import java.security.Principal; import java.util.Date; -import java.util.Map; +import java.util.Properties; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.license.LicenseDescriptor; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.extensions.surf.util.I18NUtil; /** @@ -45,6 +48,10 @@ public class DescriptorStartupLog extends AbstractLifecycleBean // Dependencies private DescriptorService descriptorService; private TenantService tenantService; + private TransactionService transactionService; + + private final String SYSTEM_INFO_STARTUP = "system.info.startup"; + private final String SYSTEM_WARN_READONLY = "system.warn.readonly"; /** * @param descriptorService Descriptor Service @@ -61,8 +68,15 @@ public class DescriptorStartupLog extends AbstractLifecycleBean { this.tenantService = tenantService; } - - + + /** + * @param transactionService service to tell about read-write mode + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + /** * Get Organisation from Principal * @@ -99,7 +113,7 @@ public class DescriptorStartupLog extends AbstractLifecycleBean // // log output of VM stats // - Map properties = System.getProperties(); + Properties properties = System.getProperties(); String version = (properties.get("java.runtime.version") == null) ? "unknown" : (String)properties.get("java.runtime.version"); long maxHeap = Runtime.getRuntime().maxMemory(); float maxHeapMB = maxHeap / 1024l; @@ -124,27 +138,44 @@ public class DescriptorStartupLog extends AbstractLifecycleBean LicenseDescriptor license = descriptorService.getLicenseDescriptor(); if (license != null && logger.isInfoEnabled()) { - String subject = license.getSubject(); - String msg = "Alfresco license: " + subject; + LicenseMode licenseMode = license.getLicenseMode(); + + String msg = "Alfresco license: Mode " + licenseMode; + String holder = getHolderOrganisation(license.getHolder()); if (holder != null) { msg += " granted to " + holder; } + Date validUntil = license.getValidUntil(); + if (validUntil != null) { Integer days = license.getDays(); Integer remainingDays = license.getRemainingDays(); - msg += " limited to " + days + " days expiring " + validUntil + " (" + remainingDays + " days remaining)"; + msg += " limited to " + days + " days expiring " + validUntil + " (" + remainingDays + " days remaining)."; } else { - msg += " (does not expire)"; + msg += " (does not expire)."; } + Long maxUsers = license.getMaxUsers(); + if (maxUsers != null) + { + msg += " User limit is " + maxUsers + "."; + } + Long maxDocs = license.getMaxDocs(); + if (maxDocs != null) + { + msg += " Document limit is " + maxDocs + "."; + } + /** + * This is an important information logging since it logs the license + */ logger.info(msg); } @@ -152,28 +183,39 @@ public class DescriptorStartupLog extends AbstractLifecycleBean if (logger.isInfoEnabled()) { Descriptor serverDescriptor = descriptorService.getServerDescriptor(); + Descriptor currentDescriptor = descriptorService.getCurrentRepositoryDescriptor(); Descriptor installedRepoDescriptor = descriptorService.getInstalledRepositoryDescriptor(); + String serverEdition = serverDescriptor.getEdition(); - if (tenantService.isEnabled()) - { - serverEdition = serverEdition + " - Multi-Tenant"; - } + String currentVersion = currentDescriptor.getVersion(); + int currentSchemaVersion = currentDescriptor.getSchema(); + LicenseMode currentMode = currentDescriptor.getLicenseMode(); - String serverVersion = serverDescriptor.getVersion(); - int serverSchemaVersion = serverDescriptor.getSchema(); String installedRepoVersion = installedRepoDescriptor.getVersion(); int installedSchemaVersion = installedRepoDescriptor.getSchema(); - logger.info(String.format("Alfresco started (%s): Current version %s schema %d - Originally installed version %s schema %d", - serverEdition, serverVersion, serverSchemaVersion, installedRepoVersion, installedSchemaVersion)); + + /** + * Alfresco started + */ + Object[] params = new Object[] { + serverEdition, + currentMode != LicenseMode.TEAM ? "" : (" " + currentMode), // only append TEAM + !tenantService.isEnabled() ? "" : (" Multi-Tenant"), + currentVersion, currentSchemaVersion, installedRepoVersion, installedSchemaVersion}; + logger.info(I18NUtil.getMessage(SYSTEM_INFO_STARTUP, params)); + } + + // Issue a warning if the system is in read-only mode + if (!transactionService.getAllowWrite()) + { + logger.warn(I18NUtil.getMessage(SYSTEM_WARN_READONLY)); } } - @Override protected void onShutdown(ApplicationEvent event) { // NOOP } - } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/descriptor/LicenseResourceComponent.java b/source/java/org/alfresco/repo/descriptor/LicenseResourceComponent.java new file mode 100644 index 0000000000..71cd670fd4 --- /dev/null +++ b/source/java/org/alfresco/repo/descriptor/LicenseResourceComponent.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.descriptor; + +/** + * The licence resource component knows the locations where license files + * may be found. + * + * Locations are suitable to be loaded by spring's getResource method. + * + */ +public class LicenseResourceComponent +{ + public LicenseResourceComponent() + { + } + + private String externalLicenseLocation = "*.lic"; + private String embeddedLicenseLocation = "/WEB-INF/alfresco/license/*.lic"; + private String sharedLicenseLocation = "classpath*:/alfresco/extension/license/*.lic"; + + public void setExternalLicenseLocation(String externalLicenseLocation) + { + this.externalLicenseLocation = externalLicenseLocation; + } + public String getExternalLicenseLocation() + { + return externalLicenseLocation; + } + public void setEmbeddedLicenseLocation(String embeddedLicenseLocation) + { + this.embeddedLicenseLocation = embeddedLicenseLocation; + } + public String getEmbeddedLicenseLocation() + { + return embeddedLicenseLocation; + } + public void setSharedLicenseLocation(String sharedLicenseLocation) + { + this.sharedLicenseLocation = sharedLicenseLocation; + } + public String getSharedLicenseLocation() + { + return sharedLicenseLocation; + } +} diff --git a/source/java/org/alfresco/repo/descriptor/RepositoryDescriptorDAOImpl.java b/source/java/org/alfresco/repo/descriptor/RepositoryDescriptorDAOImpl.java index e37051f637..2265362223 100644 --- a/source/java/org/alfresco/repo/descriptor/RepositoryDescriptorDAOImpl.java +++ b/source/java/org/alfresco/repo/descriptor/RepositoryDescriptorDAOImpl.java @@ -32,6 +32,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.descriptor.DescriptorServiceImpl.BaseDescriptor; import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; @@ -55,29 +56,13 @@ import org.apache.commons.logging.LogFactory; */ public class RepositoryDescriptorDAOImpl implements DescriptorDAO { - - /** The logger. */ private static Log logger = LogFactory.getLog(RepositoryDescriptorDAOImpl.class); - - /** The name. */ private String name; - - /** The node service. */ private NodeService nodeService; - - /** The content service. */ private ContentService contentService; - - /** The search service. */ private SearchService searchService; - - /** The namespace service. */ private NamespaceService namespaceService; - - /** The system bootstrap. */ private ImporterBootstrap systemBootstrap; - - /** The transaction service. */ private TransactionService transactionService; /** @@ -157,10 +142,7 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO this.transactionService = transactionService; } - /* - * (non-Javadoc) - * @see org.alfresco.repo.descriptor.DescriptorPersistence#getDescriptor() - */ + @Override public Descriptor getDescriptor() { // retrieve system descriptor @@ -175,12 +157,8 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO return null; } - /* - * (non-Javadoc) - * @see - * org.alfresco.repo.descriptor.DescriptorPersistence#updateDescriptor(org.alfresco.service.descriptor.Descriptor) - */ - public Descriptor updateDescriptor(final Descriptor serverDescriptor) + @Override + public Descriptor updateDescriptor(final Descriptor serverDescriptor, LicenseMode licenseMode) { final NodeRef currentDescriptorNodeRef = getDescriptorNodeRef(true); // if the node is missing but it should have been created @@ -199,6 +177,8 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO props.put(ContentModel.PROP_SYS_VERSION_LABEL, serverDescriptor.getVersionLabel()); props.put(ContentModel.PROP_SYS_VERSION_BUILD, serverDescriptor.getVersionBuild()); props.put(ContentModel.PROP_SYS_VERSION_SCHEMA, serverDescriptor.getSchema()); + props.put(ContentModel.PROP_SYS_LICENSE_MODE, licenseMode); + this.nodeService.addProperties(currentDescriptorNodeRef, props); // ALF-726: v3.1.x Content Cleaner Job needs to be ported to v3.2 @@ -227,10 +207,7 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO return new RepositoryDescriptor(properties); } - /* - * (non-Javadoc) - * @see org.alfresco.repo.descriptor.DescriptorPersistence#getLicenseKey() - */ + @Override public byte[] getLicenseKey() { byte[] key = null; @@ -242,7 +219,8 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO { throw new LicenseException("Failed to find system descriptor"); } - final ContentReader reader = this.contentService.getReader(descriptorRef, + final ContentReader reader = this.contentService.getReader( + descriptorRef, ContentModel.PROP_SYS_VERSION_EDITION); if (reader != null && reader.exists()) { @@ -253,15 +231,12 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO } catch (final Exception e) { - throw new LicenseException("Failed to load license", e); + throw new LicenseException("Failed to load license key: " + e.getMessage(), e); } return key; } - /* - * (non-Javadoc) - * @see org.alfresco.repo.descriptor.DescriptorPersistence#updateLicenseKey(byte[]) - */ + @Override public void updateLicenseKey(final byte[] key) { try @@ -277,7 +252,8 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO } else { - final ContentWriter writer = this.contentService.getWriter(descriptorRef, + final ContentWriter writer = this.contentService.getWriter( + descriptorRef, ContentModel.PROP_SYS_VERSION_EDITION, true); final InputStream is = new ByteArrayInputStream(key); writer.setMimetype(MimetypeMap.MIMETYPE_BINARY); @@ -286,7 +262,7 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO } catch (final Exception e) { - throw new LicenseException("Failed to save license", e); + throw new LicenseException("Failed to save license: " + e.getMessage(), e); } } @@ -377,91 +353,51 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO this.properties = properties; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getId() - */ public String getId() { return getDescriptor("sys:node-uuid"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getName() - */ public String getName() { return getDescriptor("sys:name"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionMajor() - */ public String getVersionMajor() { return getDescriptor("sys:versionMajor"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionMinor() - */ public String getVersionMinor() { return getDescriptor("sys:versionMinor"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionRevision() - */ public String getVersionRevision() { return getDescriptor("sys:versionRevision"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionLabel() - */ public String getVersionLabel() { return getDescriptor("sys:versionLabel"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionBuild() - */ public String getVersionBuild() { return getDescriptor("sys:versionBuild"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getEdition() - */ public String getEdition() { return getDescriptor("sys:versionEdition"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getSchema() - */ public int getSchema() { return getSchema(getDescriptor("sys:versionSchema")); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getDescriptorKeys() - */ public String[] getDescriptorKeys() { final String[] keys = new String[this.properties.size()]; @@ -469,10 +405,6 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO return keys; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getDescriptor(java.lang.String) - */ public String getDescriptor(final String key) { String strValue = null; @@ -495,6 +427,12 @@ public class RepositoryDescriptorDAOImpl implements DescriptorDAO } return strValue; } - } + @Override + public LicenseMode getLicenseMode() + { + String licenseModeStr = getDescriptor("sys:licenseMode"); + return licenseModeStr == null ? LicenseMode.UNKNOWN : LicenseMode.valueOf(licenseModeStr); + } + } } diff --git a/source/java/org/alfresco/repo/descriptor/ServerDescriptorDAOImpl.java b/source/java/org/alfresco/repo/descriptor/ServerDescriptorDAOImpl.java index 9002fd3e69..fd44eaab54 100644 --- a/source/java/org/alfresco/repo/descriptor/ServerDescriptorDAOImpl.java +++ b/source/java/org/alfresco/repo/descriptor/ServerDescriptorDAOImpl.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.util.Properties; import org.alfresco.repo.descriptor.DescriptorServiceImpl.BaseDescriptor; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; import org.alfresco.service.descriptor.Descriptor; import org.springframework.core.io.Resource; @@ -73,39 +74,24 @@ public class ServerDescriptorDAOImpl implements DescriptorDAO } } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.DescriptorPersistence#getDescriptor() - */ public Descriptor getDescriptor() { return new ServerDescriptor(); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.DescriptorPersistence#getLicenseKey() - */ public byte[] getLicenseKey() { throw new UnsupportedOperationException(); } - /* - * (non-Javadoc) - * @see - * org.alfresco.service.descriptor.DescriptorPersistence#updateDescriptor(org.alfresco.service.descriptor.Descriptor - * ) + /** + * @throws UnsupportedOperationException */ - public Descriptor updateDescriptor(final Descriptor serverDescriptor) + public Descriptor updateDescriptor(final Descriptor serverDescriptor, LicenseMode licenseMode) { throw new UnsupportedOperationException(); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.DescriptorPersistence#updateLicenseKey(byte[]) - */ public void updateLicenseKey(final byte[] key) { throw new UnsupportedOperationException(); @@ -116,92 +102,52 @@ public class ServerDescriptorDAOImpl implements DescriptorDAO */ private class ServerDescriptor extends BaseDescriptor { - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getId() - */ public String getId() { return "Unknown"; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getName() - */ public String getName() { return ServerDescriptorDAOImpl.this.repositoryName == null ? "" : ServerDescriptorDAOImpl.this.repositoryName; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionMajor() - */ public String getVersionMajor() { return ServerDescriptorDAOImpl.this.serverProperties.getProperty("version.major"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionMinor() - */ public String getVersionMinor() { return ServerDescriptorDAOImpl.this.serverProperties.getProperty("version.minor"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionRevision() - */ public String getVersionRevision() { return ServerDescriptorDAOImpl.this.serverProperties.getProperty("version.revision"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionLabel() - */ public String getVersionLabel() { return ServerDescriptorDAOImpl.this.serverProperties.getProperty("version.label"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getVersionBuild() - */ public String getVersionBuild() { return ServerDescriptorDAOImpl.this.serverProperties.getProperty("version.build"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getEdition() - */ public String getEdition() { return ServerDescriptorDAOImpl.this.serverProperties.getProperty("version.edition"); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getSchema() - */ public int getSchema() { return getSchema(ServerDescriptorDAOImpl.this.serverProperties.getProperty("version.schema")); } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getDescriptorKeys() - */ public String[] getDescriptorKeys() { final String[] keys = new String[ServerDescriptorDAOImpl.this.serverProperties.size()]; @@ -209,13 +155,15 @@ public class ServerDescriptorDAOImpl implements DescriptorDAO return keys; } - /* - * (non-Javadoc) - * @see org.alfresco.service.descriptor.Descriptor#getDescriptor(java.lang.String) - */ public String getDescriptor(final String key) { return ServerDescriptorDAOImpl.this.serverProperties.getProperty(key, ""); } + + @Override + public LicenseMode getLicenseMode() + { + return LicenseMode.UNKNOWN; + } } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/domain/DomainTestSuite.java b/source/java/org/alfresco/repo/domain/DomainTestSuite.java index 20274d4e63..d0e1cb5f98 100644 --- a/source/java/org/alfresco/repo/domain/DomainTestSuite.java +++ b/source/java/org/alfresco/repo/domain/DomainTestSuite.java @@ -32,6 +32,7 @@ import org.alfresco.repo.domain.patch.AppliedPatchDAOTest; import org.alfresco.repo.domain.permissions.AclCrudDAOTest; import org.alfresco.repo.domain.propval.PropertyValueDAOTest; import org.alfresco.repo.domain.qname.QNameDAOTest; +import org.alfresco.repo.domain.query.CannedQueryDAOTest; import org.alfresco.repo.domain.usage.UsageDAOTest; /** @@ -58,6 +59,7 @@ public class DomainTestSuite extends TestSuite suite.addTestSuite(AppliedPatchDAOTest.class); suite.addTestSuite(AclCrudDAOTest.class); suite.addTestSuite(UsageDAOTest.class); + suite.addTestSuite(CannedQueryDAOTest.class); return suite; } diff --git a/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java b/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java index 93011f8042..96dc235668 100644 --- a/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java +++ b/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java @@ -40,11 +40,15 @@ public interface ActivityFeedDAO extends ActivitiesDAO public int deleteUserFeedEntries(String feedUserId, String format, Date keepDate) throws SQLException; + public int deleteUserFeedEntries(String feedUserId) throws SQLException; + public int deleteSiteFeedEntries(String siteId, String format, Date keepDate) throws SQLException; + public int deleteSiteFeedEntries(String siteUserId) throws SQLException; + public List selectFeedsToClean(int maxFeedSize) throws SQLException; - public List selectUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers) throws SQLException; + public List selectUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedItems) throws SQLException; - public List selectSiteFeedEntries(String siteUserId, String format) throws SQLException; + public List selectSiteFeedEntries(String siteUserId, String format, int maxFeedItems) throws SQLException; } diff --git a/source/java/org/alfresco/repo/domain/activities/ActivityFeedEntity.java b/source/java/org/alfresco/repo/domain/activities/ActivityFeedEntity.java index 155f0abe82..e92aa028ff 100644 --- a/source/java/org/alfresco/repo/domain/activities/ActivityFeedEntity.java +++ b/source/java/org/alfresco/repo/domain/activities/ActivityFeedEntity.java @@ -19,16 +19,30 @@ package org.alfresco.repo.domain.activities; import java.util.Date; +import java.util.HashMap; +import java.util.Map; -import org.springframework.extensions.surf.util.ISO8601DateFormat; +import org.alfresco.repo.activities.feed.FeedTaskProcessor; +import org.alfresco.util.JSONtoFmModel; import org.json.JSONException; import org.json.JSONObject; +import org.springframework.extensions.surf.util.ISO8601DateFormat; /** * Activity Feed DAO */ public class ActivityFeedEntity -{ +{ + // JSON keys + public static final String KEY_ACTIVITY_FEED_ID = "id"; + public static final String KEY_ACTIVITY_FEED_POST_DATE = "postDate"; + public static final String KEY_ACTIVITY_FEED_POST_USERID = "postUserId"; + public static final String KEY_ACTIVITY_FEED_USERID = "feedUserId"; + public static final String KEY_ACTIVITY_FEED_SITE = "siteNetwork"; + public static final String KEY_ACTIVITY_FEED_TYPE = "activityType"; + public static final String KEY_ACTIVITY_FEED_SUMMARY = "activitySummary"; + public static final String KEY_ACTIVITY_FEED_SUMMARY_FORMAT = "activitySummaryFormat"; + private Long id; // internal DB-generated id private String activityType; private String activitySummary; @@ -155,15 +169,45 @@ public class ActivityFeedEntity { JSONObject jo = new JSONObject(); - jo.put("id", id); - jo.put("postUserId", postUserId); - jo.put("postDate", ISO8601DateFormat.format(postDate)); - if (feedUserId != null) { jo.put("feedUserId", feedUserId); } // eg. site feed - jo.put("siteNetwork", siteNetwork); - jo.put("activityType", activityType); - jo.put("activitySummary", activitySummary); - jo.put("activitySummaryFormat", activitySummaryFormat); + jo.put(KEY_ACTIVITY_FEED_ID, id); + + jo.put(KEY_ACTIVITY_FEED_POST_USERID, postUserId); + jo.put(KEY_ACTIVITY_FEED_POST_DATE, ISO8601DateFormat.format(postDate)); + + if (getFeedUserId() != null) { jo.put(KEY_ACTIVITY_FEED_USERID, getFeedUserId()); } // eg. site feed + jo.put(KEY_ACTIVITY_FEED_SITE, siteNetwork); + jo.put(KEY_ACTIVITY_FEED_TYPE, getActivityType()); + jo.put(KEY_ACTIVITY_FEED_SUMMARY, getActivitySummary()); + + jo.put(KEY_ACTIVITY_FEED_SUMMARY_FORMAT, getActivitySummaryFormat()); return jo.toString(); } + + public Map getModel() throws JSONException + { + Map map = new HashMap(); + + map.put(KEY_ACTIVITY_FEED_ID, id); + + map.put(KEY_ACTIVITY_FEED_POST_USERID, getPostUserId()); + map.put(KEY_ACTIVITY_FEED_POST_DATE, getPostDate()); + + if (getFeedUserId() != null) { map.put(KEY_ACTIVITY_FEED_USERID, getFeedUserId()); } // eg. site feed + map.put(KEY_ACTIVITY_FEED_SITE, getSiteNetwork()); + map.put(KEY_ACTIVITY_FEED_TYPE, getActivityType()); + + map.put(KEY_ACTIVITY_FEED_SUMMARY_FORMAT, getActivitySummaryFormat()); + + if ((getActivitySummary() != null) && getActivitySummaryFormat().equals(FeedTaskProcessor.FEED_FORMAT_JSON)) + { + map.put(KEY_ACTIVITY_FEED_SUMMARY, JSONtoFmModel.convertJSONObjectToMap(getActivitySummary())); + } + else + { + map.put(KEY_ACTIVITY_FEED_SUMMARY, getActivitySummary()); + } + + return map; + } } diff --git a/source/java/org/alfresco/repo/domain/activities/ActivityFeedQueryEntity.java b/source/java/org/alfresco/repo/domain/activities/ActivityFeedQueryEntity.java new file mode 100644 index 0000000000..0544715cc7 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/activities/ActivityFeedQueryEntity.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.activities; + + +/** + * Entity bean to carry query parameters for alf_activity_feed + * + * @since 3.5 + */ +public class ActivityFeedQueryEntity +{ + private Long minId; + private String activitySummaryFormat; + private String feedUserId; + private String siteNetwork; + + public Long getMinId() + { + return minId; + } + + public void setMinId(Long minId) + { + this.minId = minId; + } + + public String getActivitySummaryFormat() + { + return activitySummaryFormat; + } + + public void setActivitySummaryFormat(String activitySummaryFormat) + { + this.activitySummaryFormat = activitySummaryFormat; + } + + public String getFeedUserId() + { + return feedUserId; + } + + public void setFeedUserId(String feedUserId) + { + this.feedUserId = feedUserId; + } + + public String getSiteNetwork() + { + return siteNetwork; + } + + public void setSiteNetwork(String siteNetwork) + { + this.siteNetwork = siteNetwork; + } +} diff --git a/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java b/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java index ad1474dbb1..7ecb4b4b2f 100644 --- a/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java @@ -26,6 +26,9 @@ import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedEntity; +import org.alfresco.repo.domain.activities.ActivityFeedQueryEntity; + +import com.ibatis.sqlmap.engine.execution.SqlExecutor; public class ActivityFeedDAOImpl extends IBatisSqlMapper implements ActivityFeedDAO { @@ -40,6 +43,14 @@ public class ActivityFeedDAOImpl extends IBatisSqlMapper implements ActivityFeed return getSqlMapClient().delete("alfresco.activities.delete_activity_feed_entries_older_than_date", keepDate); } + public int deleteSiteFeedEntries(String siteId) throws SQLException + { + ActivityFeedEntity params = new ActivityFeedEntity(); + params.setSiteNetwork(siteId); + + return getSqlMapClient().delete("alfresco.activities.delete_activity_feed_for_site_entries", params); + } + public int deleteSiteFeedEntries(String siteId, String format, Date keepDate) throws SQLException { ActivityFeedEntity params = new ActivityFeedEntity(); @@ -50,6 +61,7 @@ public class ActivityFeedDAOImpl extends IBatisSqlMapper implements ActivityFeed return getSqlMapClient().delete("alfresco.activities.delete_activity_feed_for_site_entries_older_than_date", params); } + public int deleteUserFeedEntries(String feedUserId, String format, Date keepDate) throws SQLException { ActivityFeedEntity params = new ActivityFeedEntity(); @@ -60,6 +72,14 @@ public class ActivityFeedDAOImpl extends IBatisSqlMapper implements ActivityFeed return getSqlMapClient().delete("alfresco.activities.delete_activity_feed_for_feeduser_entries_older_than_date", params); } + public int deleteUserFeedEntries(String feedUserId) throws SQLException + { + ActivityFeedEntity params = new ActivityFeedEntity(); + params.setFeedUserId(feedUserId); + + return getSqlMapClient().delete("alfresco.activities.delete_activity_feed_for_feeduser_entries", params); + } + @SuppressWarnings("unchecked") public List selectFeedsToClean(int maxFeedSize) throws SQLException { @@ -67,12 +87,22 @@ public class ActivityFeedDAOImpl extends IBatisSqlMapper implements ActivityFeed } @SuppressWarnings("unchecked") - public List selectUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers) throws SQLException + public List selectUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedSize) throws SQLException { - ActivityFeedEntity params = new ActivityFeedEntity(); + ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); params.setFeedUserId(feedUserId); params.setActivitySummaryFormat(format); + if (minFeedId > -1) + { + params.setMinId(minFeedId); + } + + if (maxFeedSize < 0) + { + maxFeedSize = SqlExecutor.NO_MAXIMUM_RESULTS; + } + if (siteId != null) { // given site @@ -86,17 +116,17 @@ public class ActivityFeedDAOImpl extends IBatisSqlMapper implements ActivityFeed if ((!excludeThisUser) && (!excludeOtherUsers)) { // no excludes => everyone => where feed user is me - return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser_and_site", params); + return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser_and_site", params, 0, maxFeedSize); } else if ((excludeThisUser) && (!excludeOtherUsers)) { // exclude feed user => others => where feed user is me and post user is not me - return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser_others_and_site", params); + return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser_others_and_site", params, 0, maxFeedSize); } else if ((excludeOtherUsers) && (!excludeThisUser)) { // exclude others => me => where feed user is me and post user is me - return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser_me_and_site", params); + return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser_me_and_site", params, 0, maxFeedSize); } } else @@ -111,17 +141,17 @@ public class ActivityFeedDAOImpl extends IBatisSqlMapper implements ActivityFeed if (!excludeThisUser && !excludeOtherUsers) { // no excludes => everyone => where feed user is me - return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser", params); + return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser", params, 0, maxFeedSize); } else if (excludeThisUser) { // exclude feed user => others => where feed user is me and post user is not me - return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser_others", params); + return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser_others", params, 0, maxFeedSize); } else if (excludeOtherUsers) { // exclude others => me => where feed user is me and post user is me - return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser_me", params); + return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_feeduser_me", params, 0, maxFeedSize); } } @@ -130,13 +160,18 @@ public class ActivityFeedDAOImpl extends IBatisSqlMapper implements ActivityFeed } @SuppressWarnings("unchecked") - public List selectSiteFeedEntries(String siteId, String format) throws SQLException + public List selectSiteFeedEntries(String siteId, String format, int maxFeedSize) throws SQLException { - ActivityFeedEntity params = new ActivityFeedEntity(); + ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); params.setSiteNetwork(siteId); params.setActivitySummaryFormat(format); + if (maxFeedSize < 0) + { + maxFeedSize = SqlExecutor.NO_MAXIMUM_RESULTS; + } + // for given site - return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_site", params); + return (List)getSqlMapClient().queryForList("alfresco.activities.select_activity_feed_for_site", params, 0, maxFeedSize); } } diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 81851059bb..3519ecd6b5 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -1479,6 +1479,10 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // Deleted nodeUpdate.setDeleted(true); nodeUpdate.setUpdateDeleted(true); + // Use a 'deleted' type QName + Long deletedQNameId = qnameDAO.getOrCreateQName(ContentModel.TYPE_DELETED).getFirst(); + nodeUpdate.setUpdateTypeQNameId(true); + nodeUpdate.setTypeQNameId(deletedQNameId); // Update cm:auditable Set nodeAspects = getNodeAspects(nodeId); diff --git a/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java b/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java index 44a4e66753..1305fa4a81 100644 --- a/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/patch/AbstractPatchDAOImpl.java @@ -25,7 +25,6 @@ import org.alfresco.ibatis.BatchingDAO; import org.alfresco.repo.domain.avm.AVMNodeEntity; import org.alfresco.repo.domain.contentdata.ContentDataDAO; import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.StoreRef; import com.ibatis.sqlmap.client.event.RowHandler; @@ -134,13 +133,6 @@ public abstract class AbstractPatchDAOImpl implements PatchDAO, BatchingDAO return deleteAclMemberEntitiesForAcls(aclIds); } - public void getUsersWithoutUsageProp(StoreRef storeRef, StringHandler handler) - { - selectUsersWithoutUsageProp(storeRef, handler); - } - - protected abstract void selectUsersWithoutUsageProp(StoreRef storeRef,StringHandler handler); - /** * {@inheritDoc} *

diff --git a/source/java/org/alfresco/repo/domain/patch/PatchDAO.java b/source/java/org/alfresco/repo/domain/patch/PatchDAO.java index e6996eef98..526609c1b7 100644 --- a/source/java/org/alfresco/repo/domain/patch/PatchDAO.java +++ b/source/java/org/alfresco/repo/domain/patch/PatchDAO.java @@ -25,7 +25,6 @@ import org.alfresco.repo.domain.avm.AVMNodeEntity; import org.alfresco.repo.domain.contentdata.ContentDataDAO; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; @@ -89,13 +88,9 @@ public interface PatchDAO } /** - * Iterate over all person nodes with missing usage property (for one-off patch) - * - * @param storeRef the store to search in - * @param handler the callback to use while iterating over the people - * @return Returns the values for person node uuid + * Add a cm:sizeCurrent property to person nodes that don't have it. */ - public void getUsersWithoutUsageProp(StoreRef storeRef, StringHandler handler); + public int addSizeCurrentProp(); // ACL-related diff --git a/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java b/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java index b603a85aaa..6d69a41b8c 100644 --- a/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java @@ -29,6 +29,7 @@ import org.alfresco.ibatis.IdsEntity; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.CrcHelper; import org.alfresco.repo.domain.avm.AVMNodeEntity; +import org.alfresco.repo.domain.locale.LocaleDAO; import org.alfresco.repo.domain.patch.AbstractPatchDAOImpl; import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.service.cmr.repository.NodeRef; @@ -61,7 +62,6 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl private static final String SELECT_ADM_MAX_NODE_ID = "alfresco.patch.select_admMaxNodeId"; private static final String SELECT_AVM_NODES_WITH_OLD_CONTENT_PROPERTIES = "alfresco.patch.select_avmNodesWithOldContentProperties"; private static final String SELECT_ADM_OLD_CONTENT_PROPERTIES = "alfresco.patch.select_admOldContentProperties"; - private static final String SELECT_USERS_WITHOUT_USAGE_PROP = "alfresco.usage.select_GetUsersWithoutUsageProp"; private static final String SELECT_AUTHORITIES_AND_CRC = "alfresco.patch.select_authoritiesAndCrc"; private static final String SELECT_PERMISSIONS_ALL_ACL_IDS = "alfresco.patch.select_AllAclIds"; private static final String SELECT_PERMISSIONS_USED_ACL_IDS = "alfresco.patch.select_UsedAclIds"; @@ -78,6 +78,7 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl private static final String UPDATE_AVM_NODE_LIST_NULLIFY_ACL = "alfresco.avm.update_AVMNodeList_nullifyAcl"; private static final String UPDATE_AVM_NODE_LIST_SET_ACL = "alfresco.avm.update_AVMNodeList_setAcl"; private static final String UPDATE_CHILD_ASSOC_CRC = "alfresco.patch.update_childAssocCrc"; + private static final String UPDATE_CREATE_SIZE_CURRENT_PROPERTY = "alfresco.patch.update_CreateSizeCurrentProperty"; private static final String DELETE_PERMISSIONS_UNUSED_ACES = "alfresco.permissions.delete_UnusedAces"; private static final String DELETE_PERMISSIONS_ACL_LIST = "alfresco.permissions.delete_AclList"; @@ -96,6 +97,7 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl private SqlMapClientTemplate template; private QNameDAO qnameDAO; + private LocaleDAO localeDAO; public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate) { @@ -107,6 +109,11 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl this.qnameDAO = qnameDAO; } + public void setLocaleDAO(LocaleDAO localeDAO) + { + this.localeDAO = localeDAO; + } + public void startBatch() { try @@ -226,50 +233,23 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl } @Override - protected void selectUsersWithoutUsageProp(StoreRef storeRef, StringHandler handler) + public int addSizeCurrentProp() { - long personTypeQNameEntityId = qnameDAO.getOrCreateQName(ContentModel.TYPE_PERSON).getFirst(); - long sizeCurrentPropQNameEntityId = qnameDAO.getOrCreateQName(ContentModel.PROP_SIZE_CURRENT).getFirst(); + Long sizeCurrentPropQNameId = qnameDAO.getOrCreateQName(ContentModel.PROP_SIZE_CURRENT).getFirst(); + Long defaultLocaleId = localeDAO.getOrCreateDefaultLocalePair().getFirst(); + Long personTypeQNameId = qnameDAO.getOrCreateQName(ContentModel.TYPE_PERSON).getFirst(); - Map params = new HashMap(1); - params.put("personTypeQNameID", personTypeQNameEntityId); // cm:person - params.put("sizeCurrentPropQNameID",sizeCurrentPropQNameEntityId); // cm:sizeCurrent - params.put("storeProtocol", storeRef.getProtocol()); - params.put("storeIdentifier", storeRef.getIdentifier()); - params.put("isDeleted", false); - - StringRowHandler rowHandler = new StringRowHandler(handler); - - template.queryWithRowHandler(SELECT_USERS_WITHOUT_USAGE_PROP, params, rowHandler); + SizeCurrentParams params = new SizeCurrentParams(); + params.setSizeCurrentQNameId(sizeCurrentPropQNameId); + params.setDefaultLocaleId(defaultLocaleId); + params.setPersonTypeQNameId(personTypeQNameId); + int rowsAffected = template.update(UPDATE_CREATE_SIZE_CURRENT_PROPERTY, params); if (logger.isDebugEnabled()) { - logger.debug(" Listed " + rowHandler.total + " users without usage"); - } - } - - /** - * Row handler for getting strings - */ - private static class StringRowHandler implements RowHandler - { - private final StringHandler handler; - - private int total = 0; - - private StringRowHandler(StringHandler handler) - { - this.handler = handler; - } - public void handleRow(Object valueObject) - { - handler.handle((String)valueObject); - total++; - if (logger.isDebugEnabled() && (total == 0 || (total % 1000 == 0) )) - { - logger.debug(" Listed " + total + " strings"); - } + logger.debug("Added " + rowsAffected + " cm:sizeCurrent properties."); } + return rowsAffected; } @Override diff --git a/source/java/org/alfresco/repo/domain/patch/ibatis/SizeCurrentParams.java b/source/java/org/alfresco/repo/domain/patch/ibatis/SizeCurrentParams.java new file mode 100644 index 0000000000..8f96f03649 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/patch/ibatis/SizeCurrentParams.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.patch.ibatis; + +/** + * Parameters for the cm:sizeCurrent update. + */ +public class SizeCurrentParams +{ + private Long sizeCurrentQNameId; + private Long personTypeQNameId; + private Long defaultLocaleId; + + public boolean getFalse() + { + return false; + } + + public Long getSizeCurrentQNameId() + { + return sizeCurrentQNameId; + } + public void setSizeCurrentQNameId(Long sizeCurrentQNameId) + { + this.sizeCurrentQNameId = sizeCurrentQNameId; + } + public Long getPersonTypeQNameId() + { + return personTypeQNameId; + } + public void setPersonTypeQNameId(Long personTypeQNameId) + { + this.personTypeQNameId = personTypeQNameId; + } + public Long getDefaultLocaleId() + { + return defaultLocaleId; + } + public void setDefaultLocaleId(Long defaultLocaleId) + { + this.defaultLocaleId = defaultLocaleId; + } +} diff --git a/source/java/org/alfresco/repo/domain/permissions/ADMPermissionsDaoComponentImpl.java b/source/java/org/alfresco/repo/domain/permissions/ADMPermissionsDaoComponentImpl.java index 90b4cccae3..a6e96ebc11 100644 --- a/source/java/org/alfresco/repo/domain/permissions/ADMPermissionsDaoComponentImpl.java +++ b/source/java/org/alfresco/repo/domain/permissions/ADMPermissionsDaoComponentImpl.java @@ -105,7 +105,6 @@ public class ADMPermissionsDaoComponentImpl extends AbstractPermissionsDaoCompon { return; } - System.out.println("Deleting "+acl+" on "+nodeRef); if (acl != null) { switch (acl.getAclType()) diff --git a/source/java/org/alfresco/repo/domain/query/AbstractCannedQueryDAOImpl.java b/source/java/org/alfresco/repo/domain/query/AbstractCannedQueryDAOImpl.java new file mode 100644 index 0000000000..738694d019 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/query/AbstractCannedQueryDAOImpl.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.query; + +import org.alfresco.repo.domain.control.ControlDAO; +import org.alfresco.repo.domain.query.CannedQueryDAO; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * DAO implementation providing canned query support. + * + * @author Derek Hulley + * @since 3.5 + */ +public abstract class AbstractCannedQueryDAOImpl implements CannedQueryDAO +{ + protected Log logger = LogFactory.getLog(this.getClass()); + + protected ControlDAO controlDAO; + + /** + * @param controlDAO the DAO that allows controlled rollback, if required + */ + public void setControlDAO(ControlDAO controlDAO) + { + this.controlDAO = controlDAO; + } + + /** + * Checks that properties have been set + */ + public void init() + { + PropertyCheck.mandatory(this, "controlDAO", controlDAO); + } +} diff --git a/source/java/org/alfresco/repo/domain/query/CannedQueryDAO.java b/source/java/org/alfresco/repo/domain/query/CannedQueryDAO.java new file mode 100644 index 0000000000..4ae6d0d774 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/query/CannedQueryDAO.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.query; + +/** + * DAO services for general-use canned queries + * + * @author Derek Hulley + * @since 3.5 + */ +public interface CannedQueryDAO +{ + /** + * Execute a count(*)-style query returning a count value. The implementation + * will ensure that null is substituted with 0, if required. + *

+ * All exceptions can be safely caught and handled as required. + * + * @param sqlNamespace the query namespace (defined by config file) e.g. alfresco.query.usage + * @param queryName the name of the query e.g. select_userCount + * @param parameterObj the values to drive the selection + * @return a non-null count + * + * @throws QueryException if the query returned multiple results + */ + Long executeCountQuery(String sqlNamespace, String queryName, Object parameterObj); +} diff --git a/source/java/org/alfresco/repo/domain/query/CannedQueryDAOTest.java b/source/java/org/alfresco/repo/domain/query/CannedQueryDAOTest.java new file mode 100644 index 0000000000..b6780dca95 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/query/CannedQueryDAOTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.query; + +import junit.framework.TestCase; + +import org.alfresco.repo.domain.mimetype.MimetypeDAO; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.GUID; +import org.springframework.context.ApplicationContext; + +/** + * @see CannedQueryDAO + * + * @author Derek Hulley + * @since 3.2 + */ +public class CannedQueryDAOTest extends TestCase +{ + private static final String QUERY_NS = "alfresco.query.test"; + private static final String QUERY_SELECT_MIMETYPE_COUNT = "select_CountMimetypes"; + + private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private TransactionService transactionService; + private RetryingTransactionHelper txnHelper; + private CannedQueryDAO cannedQueryDAO; + private MimetypeDAO mimetypeDAO; + + private String mimetypePrefix; + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + transactionService = serviceRegistry.getTransactionService(); + txnHelper = transactionService.getRetryingTransactionHelper(); + + cannedQueryDAO = (CannedQueryDAO) ctx.getBean("cannedQueryDAO"); + mimetypeDAO = (MimetypeDAO) ctx.getBean("mimetypeDAO"); + + RetryingTransactionCallback createMimetypeCallback = new RetryingTransactionCallback() + { + @Override + public String execute() throws Throwable + { + String mimetypePrefix = GUID.generate(); + mimetypeDAO.getOrCreateMimetype(mimetypePrefix + "-aaa"); + mimetypeDAO.getOrCreateMimetype(mimetypePrefix + "-bbb"); + return mimetypePrefix; + } + }; + mimetypePrefix = txnHelper.doInTransaction(createMimetypeCallback); + } + + /** + * Helper parameter class for testing + * @author Derek Hulley + */ + public static class TestOneParams + { + private final String mimetypeMatch; + private final boolean exact; + private boolean forceFail; // Trigger a SQL exception + public TestOneParams(String mimetypeMatch, boolean exact) + { + this.mimetypeMatch = mimetypeMatch; + this.exact = exact; + this.forceFail = false; + } + @Override + public String toString() + { + return "TestOneParams [mimetypeMatch=" + mimetypeMatch + ", exact=" + exact + "]"; + } + public String getMimetypeMatch() + { + return mimetypeMatch; + } + public boolean isExact() + { + return exact; + } + public boolean isForceFail() + { + return forceFail; + } + public void setForceFail(boolean forceFail) + { + this.forceFail = forceFail; + } + } + + /** + * Force a failure and ensure that the connection is not tarnished + */ + public void testExecute_FailureRecovery() throws Throwable + { + RetryingTransactionCallback failCallback = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + TestOneParams params = new TestOneParams(null, true); + params.setForceFail(true); + try + { + cannedQueryDAO.executeCountQuery(QUERY_NS, QUERY_SELECT_MIMETYPE_COUNT, params); + fail("Expected bad SQL"); + } + catch (Throwable e) + { + // Expected + } + // Now attempt to write to the connection + mimetypeDAO.getOrCreateMimetype(mimetypePrefix + "-postfail"); + return null; + } + }; + txnHelper.doInTransaction(failCallback, false); + } + + public void testExecute_CountAllMimetypes() throws Throwable + { + RetryingTransactionCallback selectCallback = new RetryingTransactionCallback() + { + @Override + public Long execute() throws Throwable + { + TestOneParams params = new TestOneParams(null, true); + return cannedQueryDAO.executeCountQuery(QUERY_NS, QUERY_SELECT_MIMETYPE_COUNT, params); + } + }; + Long count = txnHelper.doInTransaction(selectCallback, true); + assertNotNull(count); + assertTrue(count.longValue() > 0L); + } + + /** + * Ensures that no results returns 0 since SQL will return a null count. + */ + public void testExecute_CountNoResults() throws Throwable + { + RetryingTransactionCallback selectCallback = new RetryingTransactionCallback() + { + @Override + public Long execute() throws Throwable + { + TestOneParams params = new TestOneParams(GUID.generate(), true); + return cannedQueryDAO.executeCountQuery(QUERY_NS, QUERY_SELECT_MIMETYPE_COUNT, params); + } + }; + Long count = txnHelper.doInTransaction(selectCallback, true); + assertNotNull(count); + assertEquals("Incorrect result count.", 0L, count.longValue()); + } + + public void testExecute_CountMimetypeExact() throws Throwable + { + RetryingTransactionCallback selectCallback = new RetryingTransactionCallback() + { + @Override + public Long execute() throws Throwable + { + TestOneParams params = new TestOneParams(mimetypePrefix + "-aaa", true); + return cannedQueryDAO.executeCountQuery(QUERY_NS, QUERY_SELECT_MIMETYPE_COUNT, params); + } + }; + Long count = txnHelper.doInTransaction(selectCallback, true); + assertNotNull(count); + assertEquals("Incorrect result count.", 1L, count.longValue()); + } + + public void testExecute_CountMimetypeWildcard() throws Throwable + { + RetryingTransactionCallback selectCallback = new RetryingTransactionCallback() + { + @Override + public Long execute() throws Throwable + { + TestOneParams params = new TestOneParams(mimetypePrefix + "%", false); + return cannedQueryDAO.executeCountQuery(QUERY_NS, QUERY_SELECT_MIMETYPE_COUNT, params); + } + }; + Long count = txnHelper.doInTransaction(selectCallback, true); + assertNotNull(count); + assertEquals("Incorrect result count.", 2L, count.longValue()); + } +} diff --git a/source/java/org/alfresco/repo/domain/query/QueryException.java b/source/java/org/alfresco/repo/domain/query/QueryException.java new file mode 100644 index 0000000000..b26845f9ea --- /dev/null +++ b/source/java/org/alfresco/repo/domain/query/QueryException.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.query; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Exception generated by failures to execute canned queries. + * + * @author Derek Hulley + * @since 3.5 + */ +public class QueryException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -7827116537885580234L; + + /** + * @param msg the message + */ + public QueryException(String msg) + { + super(msg); + } + + /** + * @param msg the message + * @param cause the exception cause + */ + public QueryException(String msg, Throwable cause) + { + super(msg, cause); + } + + /** + * Constructor + * + * @param msgId the message id + * @param msgParams the message parameters + */ + public QueryException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + /** + * Constructor + * + * @param msgId the message id + * @param msgParams the message parameters + * @param cause the exception cause + */ + public QueryException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } +} diff --git a/source/java/org/alfresco/repo/domain/query/ibatis/CannedQueryDAOImpl.java b/source/java/org/alfresco/repo/domain/query/ibatis/CannedQueryDAOImpl.java new file mode 100644 index 0000000000..6edfbb4239 --- /dev/null +++ b/source/java/org/alfresco/repo/domain/query/ibatis/CannedQueryDAOImpl.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.query.ibatis; + +import java.sql.Savepoint; + +import org.alfresco.repo.domain.query.AbstractCannedQueryDAOImpl; +import org.alfresco.repo.domain.query.QueryException; +import org.alfresco.util.PropertyCheck; +import org.springframework.orm.ibatis.SqlMapClientTemplate; + +/** + * DAO implementation providing canned query support. + * + * @author Derek Hulley + * @since 3.5 + */ +public class CannedQueryDAOImpl extends AbstractCannedQueryDAOImpl +{ + private SqlMapClientTemplate template; + + public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate) + { + this.template = sqlMapClientTemplate; + } + + @Override + public void init() + { + super.init(); + PropertyCheck.mandatory(this, "template", template); + } + + /** + * {@inheritDoc} + *

+ * Only one return value is allowed and is checked to prevent null returns. + */ + @Override + public Long executeCountQuery(String sqlNamespace, String queryName, Object parameterObj) + { + String query = new StringBuilder(sqlNamespace.length() + queryName.length() + 1) + .append(sqlNamespace).append(".").append(queryName).toString(); + + try + { + Long result = (Long) template.queryForObject(query, parameterObj); + if (result == null) + { + result = 0L; + } + + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Executed query: \n" + + " Query: " + query + "\n" + + " Params: " + parameterObj + "\n" + + " Result: " + result); + } + return result; + } + catch (ClassCastException e) + { + throw new QueryException( + "Count query results must return exactly one Long value: \n" + + " Query: " + query + "\n" + + " Params: " + parameterObj, + e); + } + catch (Throwable e) + { + throw new QueryException( + "Failed to execute query: \n" + + " Query: " + query + "\n" + + " Params: " + parameterObj, + e); + } + } +} diff --git a/source/java/org/alfresco/repo/exporter/AVMZipExporterServiceImpl.java b/source/java/org/alfresco/repo/exporter/AVMZipExporterServiceImpl.java new file mode 100644 index 0000000000..0e94044b5e --- /dev/null +++ b/source/java/org/alfresco/repo/exporter/AVMZipExporterServiceImpl.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.exporter; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.SortedMap; +import java.util.zip.ZipException; + +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.view.AVMZipExporterService; +import org.apache.poi.util.IOUtils; +import org.apache.tools.zip.ZipEntry; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Exporter which allows the saving of part of an AVM + * filesystem to a Zip file. + * + * @author Nick Burch + */ +public class AVMZipExporterServiceImpl implements AVMZipExporterService +{ + private AVMService avmService; + + /** + * Sets the AVM Service to be used for exporting from + * + * @param avmService The AVM Service + */ + public void setAvmService(AVMService avmService) + { + this.avmService = avmService; + } + + /** + * Exports the given path and version as a zip file, stored + * in the specified file. + * + * @param output The File to store the Zip in + * @param path The AVM path to export + * @param version The AVM version IO + * @param recurse Should the export recurse into directories? + */ + public void export(File output, int version, String path, boolean recurse) + throws IOException, ZipException + { + ZipOutputStream out = new ZipOutputStream(output); + export(out, version, path, recurse); + out.close(); + } + + /** + * Exports the given path and version into an already open + * Zip file. This method can be used to output multiple different + * AVM resources into one file. + * + * @param output The File to store the Zip in + * @param path The AVM path to export + * @param version The AVM version IO + * @param recurse Should the export recurse into directories? + */ + public void export(ZipOutputStream out, int version, String path, boolean recurse) + throws IOException, ZipException + { + AVMNodeDescriptor node = avmService.lookup(version, path); + export(out, node, recurse); + } + + /** + * Exports the given AVM node into an already open + * Zip file. This method can be used to output multiple different + * AVM resources into one file. + * + * @param output The File to store the Zip in + * @param node The AVM node to export + * @param recurse Should the export recurse into directories? + */ + public void export(ZipOutputStream out, AVMNodeDescriptor node, boolean recurse) + throws IOException, ZipException + { + // Create the zip entry + ZipEntry entry; + + // store:/foo/bar -> foo/bar/ + // store:/foo/baz.txt -> foo/baz.txt + String name = node.getPath().substring( + node.getPath().indexOf(':') + 1 + ).substring(1); + if(node.isDirectory()) { + entry = new ZipEntry(name + "/"); + } else { + entry = new ZipEntry(name); + } + out.putNextEntry(entry); + + // Output the contents + if(node.isDirectory()) { + if(recurse) { + SortedMap contents = + avmService.getDirectoryListing(node); + for(AVMNodeDescriptor child : contents.values()) { + export(out, child, recurse); + } + } + } else { + InputStream stream = avmService.getFileInputStream(node); + IOUtils.copy(stream, out); + } + } +} diff --git a/source/java/org/alfresco/repo/exporter/ExporterComponent.java b/source/java/org/alfresco/repo/exporter/ExporterComponent.java index c60a418b5e..99dcceb6db 100644 --- a/source/java/org/alfresco/repo/exporter/ExporterComponent.java +++ b/source/java/org/alfresco/repo/exporter/ExporterComponent.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -67,9 +68,9 @@ 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.springframework.extensions.surf.util.ParameterCheck; import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; +import org.springframework.extensions.surf.util.ParameterCheck; /** @@ -249,10 +250,7 @@ public class ExporterComponent */ private class DefaultCrawler implements ExporterCrawler { - private ExporterContext context; - private Map nodesWithSecondaryLinks = new HashMap(); - private Map nodesWithAssociations = new HashMap(); - + private ExporterContextImpl context; /* (non-Javadoc) * @see org.alfresco.service.cmr.view.ExporterCrawler#export(org.alfresco.service.cmr.view.Exporter) @@ -260,8 +258,6 @@ public class ExporterComponent public void export(ExporterCrawlerParameters parameters, Exporter exporter) { // Initialise Crawler - nodesWithSecondaryLinks.clear(); - nodesWithAssociations.clear(); context = new ExporterContextImpl(parameters); exporter.start(context); @@ -293,28 +289,46 @@ public class ExporterComponent } } - // - // Export Secondary Links between Nodes - // - for (NodeRef nodeWithAssociations : nodesWithSecondaryLinks.keySet()) - { - walkStartNamespaces(parameters, exporter); - walkNodeSecondaryLinks(nodeWithAssociations, parameters, exporter); - walkEndNamespaces(parameters, exporter); - } - - // - // Export Associations between Nodes - // - for (NodeRef nodeWithAssociations : nodesWithAssociations.keySet()) - { - walkStartNamespaces(parameters, exporter); - walkNodeAssociations(nodeWithAssociations, parameters, exporter); - walkEndNamespaces(parameters, exporter); - } - context.setNextValue(); } + + // + // Export associations between nodes + // + context.resetContext(); + while (context.canRetrieve()) + { + Set nodesWithSecondaryLinks = context.getNodesWithSecondaryLinks(); + if (nodesWithSecondaryLinks != null) + { + // + // Export Secondary Links between Nodes + // + for (NodeRef nodeWithAssociations : nodesWithSecondaryLinks) + { + walkStartNamespaces(parameters, exporter); + walkNodeSecondaryLinks(nodeWithAssociations, parameters, exporter); + walkEndNamespaces(parameters, exporter); + } + } + + Set nodesWithAssociations = context.getNodesWithAssociations(); + if (nodesWithAssociations != null) + { + // + // Export Associations between Nodes + // + for (NodeRef nodeWithAssociations : nodesWithAssociations) + { + walkStartNamespaces(parameters, exporter); + walkNodeAssociations(nodeWithAssociations, parameters, exporter); + walkEndNamespaces(parameters, exporter); + } + } + + context.setNextValue(); + } + exporter.end(); } @@ -515,7 +529,7 @@ public class ExporterComponent } if (childAssoc.isPrimary() == false) { - nodesWithSecondaryLinks.put(nodeRef, nodeRef); + context.recordSecondaryLink(nodeRef); continue; } if (isExcludedURI(parameters.getExcludeNamespaceURIs(), childAssoc.getQName().getNamespaceURI())) @@ -559,7 +573,7 @@ public class ExporterComponent List associations = nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL); if (associations.size() > 0) { - nodesWithAssociations.put(nodeRef, nodeRef); + context.recordAssociation(nodeRef); } } @@ -952,8 +966,12 @@ public class ExporterComponent private Date exportedDate; private String exporterVersion; + private Map> nodesWithSecondaryLinks = new HashMap>(); + private Map> nodesWithAssociations = new HashMap>(); + private int index; + /** * Construct * @@ -1071,6 +1089,67 @@ public class ExporterComponent return parentList; } + /** + * Record that associations exist for node + * + * @param nodeRef + */ + public void recordAssociation(NodeRef nodeRef) + { + Set nodes = nodesWithAssociations.get(index); + if (nodes == null) + { + nodes = new HashSet(); + nodesWithAssociations.put(index, nodes); + } + nodes.add(nodeRef); + } + + /** + * Gets nodes that have been recorded with associations + * + * @return + */ + public Set getNodesWithAssociations() + { + Set nodes = nodesWithAssociations.get(index); + if (nodes != null) + { + return nodes; + } + return null; + } + + /** + * Record that secondary links exist for node + * + * @param nodeRef + */ + public void recordSecondaryLink(NodeRef nodeRef) + { + Set nodes = nodesWithSecondaryLinks.get(index); + if (nodes == null) + { + nodes = new HashSet(); + nodesWithSecondaryLinks.put(index, nodes); + } + nodes.add(nodeRef); + } + + /** + * Gets nodes that have been recorded with secondary links + * + * @return + */ + public Set getNodesWithSecondaryLinks() + { + Set nodes = nodesWithSecondaryLinks.get(index); + if (nodes != null) + { + return nodes; + } + return null; + } /** * Get the Node Ref from the specified Location diff --git a/source/java/org/alfresco/repo/forms/FormException.java b/source/java/org/alfresco/repo/forms/FormException.java index bc1b0fc8d3..48934e1156 100644 --- a/source/java/org/alfresco/repo/forms/FormException.java +++ b/source/java/org/alfresco/repo/forms/FormException.java @@ -38,4 +38,9 @@ public class FormException extends AlfrescoRuntimeException { super(msgId, cause); } + + public FormException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } } diff --git a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java index 33e4dd2c3e..9ba8bb392b 100644 --- a/source/java/org/alfresco/repo/forms/FormServiceImplTest.java +++ b/source/java/org/alfresco/repo/forms/FormServiceImplTest.java @@ -1251,6 +1251,26 @@ public class FormServiceImplTest extends BaseAlfrescoSpringTest // retrieve the association that should now be present assocs = this.nodeService.getTargetAssocs(everythingNode, duplicateProperty); assertEquals(1, assocs.size()); + + // request a form for a type with an underscore in it's name + fields = new ArrayList(4); + fields.add("cm:name"); + + form = this.formService.getForm(new Item(TYPE_FORM_ITEM_KIND, "fdk:with_underscore"), fields); + assertNotNull(form); + + // make sure there are 3 fields + fieldDefs = form.getFieldDefinitions(); + assertNotNull(fieldDefs); + assertEquals(1, fieldDefs.size()); + + // save the form to ensure persistence works too + String nodeName = GUID.generate() + ".txt"; + data = new FormData(); + data.addFieldData("prop_cm_name", nodeName); + data.addFieldData(TypeFormProcessor.DESTINATION, this.folder.toString()); + NodeRef newNode = (NodeRef)this.formService.saveForm(new Item(TYPE_FORM_ITEM_KIND, "fdk_with_underscore"), data); + assertNotNull(newNode); } public void testGetFormForJbpmTask() throws Exception diff --git a/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java index 10a7dfaf39..804490493a 100644 --- a/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/node/TypeFormProcessor.java @@ -91,6 +91,12 @@ public class TypeFormProcessor extends ContentModelFormProcessor permissionMap; + + // TODO: need a way of indicating if a customer is a premium user or not + + /** + * List of supported GoogleDoc supported mimetypes. + * Taken from list found at http://code.google.com/apis/documents/faq.html#WhatKindOfFilesCanIUpload + * NOTE: this restriction only applies to non-premium users. + */ + // TODO make this list configurable + private List supportedMimetypes = Arrays.asList( + "text/csv", + "text/tab-separated-values", + "text/html", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/x-vnd.oasis.opendocument.spreadsheet", + "application/vnd.oasis.opendocument.text", + "application/rtf", + "application/vnd.sun.xml.writer", + "text/plain", + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/pdf", + "application/vnd.ms-powerpoint", + "image/x-wmf" + ); /** * @param googleDocumentService google document service @@ -251,7 +278,7 @@ public class GoogleDocsServiceImpl extends TransactionListenerAdapter if (username == null ||username.length() == 0 || password == null) { - throw new GoogleDocsServiceInitException("No Goolge Docs credentials found. Please set the Google Docs authentication configuration."); + throw new GoogleDocsServiceInitException("No Google Docs credentials found. Please set the Google Docs authentication configuration."); } try @@ -274,6 +301,16 @@ public class GoogleDocsServiceImpl extends TransactionListenerAdapter } /** + * @see org.alfresco.repo.googledocs.GoogleDocsService#isSupportedMimetype(java.lang.String) + */ + @Override + public boolean isSupportedMimetype(String mimetype) + { + return supportedMimetypes.contains(mimetype); + } + + /** + * @throws GoogleDocsUnsupportedMimetypeException * @see org.alfresco.google.docs.GoogleDocsService#upload(org.alfresco.service.cmr.repository.NodeRef) */ public void createGoogleDoc(NodeRef nodeRef, GoogleDocsPermissionContext permissionContext) @@ -315,6 +352,12 @@ public class GoogleDocsServiceImpl extends TransactionListenerAdapter } } + // Check that we support the mimetype + if (isSupportedMimetype(mimetype) == false) + { + throw new GoolgeDocsUnsupportedMimetypeException(nodeRef, ContentModel.PROP_CONTENT, mimetype); + } + // Get the parent folder id DocumentListEntry parentFolder = getParentFolder(nodeRef); @@ -508,13 +551,7 @@ public class GoogleDocsServiceImpl extends TransactionListenerAdapter // Create the folder and set the meta data in Alfresco folder = createGoogleFolder(name, parentFolder); - setResourceDetails(parentNodeRef, folder); - - // Set the owner of the document - setGoogleResourcePermission(folder, AuthorityType.USER, username, "owner"); - - // Set the owner of the document - setGoogleResourcePermission(folder, AuthorityType.USER, username, "owner"); + setResourceDetails(parentNodeRef, folder); } } } diff --git a/source/java/org/alfresco/repo/googledocs/GoogleDocumentServiceSystemTest.java b/source/java/org/alfresco/repo/googledocs/GoogleDocumentServiceSystemTest.java index ae61f05d09..ae98c5258a 100644 --- a/source/java/org/alfresco/repo/googledocs/GoogleDocumentServiceSystemTest.java +++ b/source/java/org/alfresco/repo/googledocs/GoogleDocumentServiceSystemTest.java @@ -23,7 +23,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; -import java.net.URL; import javax.transaction.UserTransaction; @@ -41,7 +40,6 @@ import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.rendition.RenditionDefinition; import org.alfresco.service.cmr.rendition.RenditionService; -import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; @@ -57,18 +55,9 @@ import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.GUID; import org.alfresco.util.PropertyMap; -import org.apache.axis.wsdl.toJava.NamespaceSelector; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; -import com.google.gdata.client.docs.DocsService; -import com.google.gdata.data.MediaContent; -import com.google.gdata.data.PlainTextConstruct; -import com.google.gdata.data.docs.FolderEntry; -import com.google.gdata.data.docs.SpreadsheetEntry; -import com.google.gdata.data.docs.DocumentListEntry.MediaType; -import com.google.gdata.data.media.MediaStreamSource; -import com.google.gdata.util.ContentType; import com.google.gdata.util.ServiceException; public class GoogleDocumentServiceSystemTest extends TestCase implements GoogleDocsModel diff --git a/source/java/org/alfresco/repo/googledocs/GoolgeDocsUnsupportedMimetypeException.java b/source/java/org/alfresco/repo/googledocs/GoolgeDocsUnsupportedMimetypeException.java new file mode 100644 index 0000000000..274810c870 --- /dev/null +++ b/source/java/org/alfresco/repo/googledocs/GoolgeDocsUnsupportedMimetypeException.java @@ -0,0 +1,75 @@ +/** + * + */ +package org.alfresco.repo.googledocs; + +import java.text.MessageFormat; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Roy Wetherall + */ +public class GoolgeDocsUnsupportedMimetypeException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = 7505645425492536907L; + + private static Log logger = LogFactory.getLog(GoolgeDocsUnsupportedMimetypeException.class); + + /** + * @param msgId + */ + public GoolgeDocsUnsupportedMimetypeException(String msgId) + { + super(msgId); + } + + /** + * @param msgId + * @param msgParams + */ + public GoolgeDocsUnsupportedMimetypeException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + /** + * @param msgId + * @param cause + */ + public GoolgeDocsUnsupportedMimetypeException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + /** + * @param msgId + * @param msgParams + * @param cause + */ + public GoolgeDocsUnsupportedMimetypeException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + + /** + * @param nodeRef node reference + * @param contentProp content property + * @param mimetype mimetype + */ + public GoolgeDocsUnsupportedMimetypeException(NodeRef nodeRef, QName contentProp, String mimetype) + { + // TODO I18N + this(MessageFormat.format("The mimetype {0} is not supported by Google Docs", mimetype)); + + if (logger.isDebugEnabled() == true) + { + logger.debug(MessageFormat.format("The mimetype {0} is not supported by Google Docs. (nodeRef = {1}, contentProp = {2})", mimetype, nodeRef.toString(), contentProp.toString())); + } + } + +} diff --git a/source/java/org/alfresco/repo/i18n/MessageServiceImplTest.java b/source/java/org/alfresco/repo/i18n/MessageServiceImplTest.java index c55723a098..0d26da51b9 100644 --- a/source/java/org/alfresco/repo/i18n/MessageServiceImplTest.java +++ b/source/java/org/alfresco/repo/i18n/MessageServiceImplTest.java @@ -26,11 +26,16 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import javax.transaction.UserTransaction; + import junit.framework.TestCase; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; @@ -40,6 +45,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; @@ -82,20 +88,52 @@ public class MessageServiceImplTest extends TestCase implements MessageDeployer * Test store ref */ private StoreRef testStoreRef; + + private TransactionService transactionService; + + private AuthenticationComponent authenticationComponent; + + private UserTransaction testTX; @Override protected void setUp() throws Exception - { + { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + fail("Detected a leaked transaction from a previous test."); + } + // Get the services by name from the application context messageService = (MessageService)applicationContext.getBean("messageService"); nodeService = (NodeService)applicationContext.getBean("NodeService"); authenticationService = (MutableAuthenticationService)applicationContext.getBean("AuthenticationService"); contentService = (ContentService) applicationContext.getBean("ContentService"); + transactionService = (TransactionService) applicationContext.getBean("transactionComponent"); + authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); // Re-set the current locale to be the default Locale.setDefault(Locale.ENGLISH); messageService.setLocale(Locale.getDefault()); + + testTX = transactionService.getUserTransaction(); + testTX.begin(); + authenticationComponent.setSystemUserAsCurrentUser(); + } + + @Override + protected void tearDown() throws Exception + { + if (testTX != null) + { + try + { + testTX.rollback(); + } + catch (Throwable e) {} // Ignore + } + AuthenticationUtil.clearCurrentSecurityContext(); + super.tearDown(); } private void setupRepo() throws Exception diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java index 465d06b012..f12c4796cf 100644 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java @@ -810,17 +810,22 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab @Override protected long getUidNextInternal() { - return getUidValidity(); + /** + * Can't used cached value since it may be out of date. + */ + return (Long) AuthenticationUtil.runAs(new GetUidValidityWork(folderInfo.getNodeRef(), serviceRegistry.getNodeService()), AuthenticationUtil.getSystemUserName()) + 1; } public boolean isStale() { if(uidValidity == generateUidValidity()) { + logger.debug("folder is not stale"); return false; } else { + logger.debug("folder is stale"); return true; } } @@ -833,8 +838,11 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab @Override protected long getUidValidityInternal() { - return uidValidity; - //return (Long) AuthenticationUtil.runAs(new GetUidValidityWork(folderInfo.getNodeRef(), serviceRegistry.getNodeService()), AuthenticationUtil.getSystemUserName()); + /** + * Can't used cached value uidValidity since it may be out of date. + */ + //return uidValidity; + return (Long) AuthenticationUtil.runAs(new GetUidValidityWork(folderInfo.getNodeRef(), serviceRegistry.getNodeService()), AuthenticationUtil.getSystemUserName()); } /** @@ -846,7 +854,7 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab { if(folderInfo != null) { - return (Long) AuthenticationUtil.runAs(new GetUidValidityWork(folderInfo.getNodeRef(), serviceRegistry.getNodeService()), AuthenticationUtil.getSystemUserName()); + return (Long) AuthenticationUtil.runAs(new GenerateUidValidityWork(folderInfo.getNodeRef(), serviceRegistry.getNodeService()), AuthenticationUtil.getSystemUserName()); } else { @@ -1205,13 +1213,20 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab } } - public class GetUidValidityWork implements RunAsWork + /** + * Generate UID validity + * + * In general this class will return a long UID value but if there is no + * ASPECT_IMAP_FOLDER then running this method will add the aspect and add + * initial values. Needs to be run in a read/write transaction + */ + public class GenerateUidValidityWork implements RunAsWork { private NodeService nodeService; private NodeRef folderNodeRef; - public GetUidValidityWork(NodeRef folderNodeRef, NodeService nodeService) + public GenerateUidValidityWork(NodeRef folderNodeRef, NodeService nodeService) { this.folderNodeRef = folderNodeRef; this.nodeService = nodeService; @@ -1244,6 +1259,37 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab } } + + /** + * Read only transaction to get uidvalidity + * @author mrogers + */ + public class GetUidValidityWork implements RunAsWork + { + private NodeService nodeService; + private NodeRef folderNodeRef; + + public GetUidValidityWork(NodeRef folderNodeRef, NodeService nodeService) + { + this.folderNodeRef = folderNodeRef; + this.nodeService = nodeService; + } + @Override + public Long doWork() throws Exception + { + if(nodeService.exists(folderNodeRef)) + { + long modifDate = 0L; + if (nodeService.hasAspect(folderNodeRef, ImapModel.ASPECT_IMAP_FOLDER)) + { + modifDate = ((Long) nodeService.getProperty(folderNodeRef, ImapModel.PROP_UIDVALIDITY)); + return (modifDate - YEAR_2005) / 1000; + } + } + return new Long(0); + } + + } } diff --git a/source/java/org/alfresco/repo/imap/ImapMessageTest.java b/source/java/org/alfresco/repo/imap/ImapMessageTest.java index 5265b6a0df..a2541b2d3c 100644 --- a/source/java/org/alfresco/repo/imap/ImapMessageTest.java +++ b/source/java/org/alfresco/repo/imap/ImapMessageTest.java @@ -332,7 +332,7 @@ public class ImapMessageTest extends TestCase } } - public void testMessageCache() throws Exception + public void dontTestMessageCache() throws Exception { // Create messages diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java index 89d67dedd9..95f13412a0 100644 --- a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java +++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java @@ -44,18 +44,19 @@ import org.alfresco.repo.imap.AlfrescoImapConst.ImapViewMode; import org.alfresco.repo.imap.config.ImapConfigMountPointsBean; import org.alfresco.repo.node.NodeServicePolicies.OnCreateChildAssociationPolicy; import org.alfresco.repo.node.NodeServicePolicies.OnDeleteChildAssociationPolicy; +import org.alfresco.repo.policy.Behaviour.NotificationFrequency; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.site.SiteServiceException; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.lock.NodeLockedException; import org.alfresco.service.cmr.model.FileFolderService; @@ -101,14 +102,12 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol private static final String FAVORITE_SITES = "imap.favorite.sites.list"; private static final String UIDVALIDITY_LISTENER_ALREADY_BOUND = "imap.uidvalidity.already.bound"; - private static final String INBOX = "INBOX"; - private static final String TRASH = "TRASH"; - private SysAdminParams sysAdminParams; private FileFolderService fileFolderService; private NodeService nodeService; private PermissionService permissionService; private ServiceRegistry serviceRegistry; + private BehaviourFilter policyBehaviourFilter; /** * Folders cache @@ -246,6 +245,11 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol { this.serviceRegistry = serviceRegistry; } + + public void setPolicyFilter(BehaviourFilter policyFilter) + { + this.policyBehaviourFilter = policyFilter; + } public void setImapHome(RepositoryFolderConfigBean imapHomeConfigBean) { @@ -310,6 +314,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); PropertyCheck.mandatory(this, "defaultFromAddress", defaultFromAddress); PropertyCheck.mandatory(this, "repositoryTemplatePath", repositoryTemplatePath); + PropertyCheck.mandatory(this, "policyBehaviourFilter", policyBehaviourFilter); } public void startup() @@ -567,7 +572,9 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol } else { - newFileInfo = fileFolderService.move(sourceNode.getFolderInfo().getNodeRef(), parentNodeRef, folderName); + newFileInfo = fileFolderService.move( + sourceNode.getFolderInfo().getNodeRef(), + parentNodeRef, folderName); } foldersCache.remove(oldMailboxName); @@ -1890,9 +1897,17 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol } else { - logger.debug("[checkForFlaggableAspect] Adding flaggable aspect to nodeRef: " + nodeRef); - Map aspectProperties = new HashMap(); - nodeService.addAspect(nodeRef, ImapModel.ASPECT_FLAGGABLE, aspectProperties); + try + { + policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + logger.debug("[checkForFlaggableAspect] Adding flaggable aspect to nodeRef: " + nodeRef); + Map aspectProperties = new HashMap(); + nodeService.addAspect(nodeRef, ImapModel.ASPECT_FLAGGABLE, aspectProperties); + } + finally + { + policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + } } } alreadyChecked.add(nodeRef); @@ -1915,6 +1930,9 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol String result = onetype.getClasspathTempltePath(); try { + // This query uses cm:name to find the template node(s). + // For the case where the templates are renamed, it would be better to use a QName path-based query. + final StringBuilder templateName = new StringBuilder(DICTIONARY_TEMPLATE_PREFIX).append("-").append(onetype.getTypeSubtype()).append(".ftl"); final String repositoryTemplatePath = getRepositoryTemplatePath(); int indexOfStoreDelim = repositoryTemplatePath.indexOf(StoreRef.URI_FILLER); @@ -1941,7 +1959,12 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol { throw new IllegalArgumentException(String.format("[getDefaultEmailBodyTemplate] IMAP message template '%1$s' does not exist in the path '%2$s'.", templateName, repositoryTemplatePath)); } - result = resultSet.getNodeRef(0).toString(); + final NodeRef defaultLocaleTemplate = resultSet.getNodeRef(0); + + NodeRef localisedSibling = serviceRegistry.getFileFolderService().getLocalizedSibling(defaultLocaleTemplate); + + result = localisedSibling.toString(); + resultSet.close(); } // We are catching all exceptions. E.g. search service can possibly trow an exceptions on malformed queries. diff --git a/source/java/org/alfresco/repo/importer/AVMImporter.java b/source/java/org/alfresco/repo/importer/AVMImporter.java new file mode 100644 index 0000000000..6815437c3e --- /dev/null +++ b/source/java/org/alfresco/repo/importer/AVMImporter.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.importer; + +import java.io.IOException; +import java.io.InputStream; + +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; + +/** + * Importer which allows the loading of part of an AVM + * filesystem from external resources. + * + * @author Nick Burch + */ +public interface AVMImporter +{ + /** + * Imports the given path and version from the source data. + * + * @param input The stream to read from + * @param path The AVM path to import + * @param version The AVM version ID + */ + public void importNode(InputStream input, String path) + throws IOException; + + /** + * Imports the given AVM node from the source data. + * + * @param input The stream to read from + * @param node The AVM node to import + */ + public void importNode(InputStream input, AVMNodeDescriptor node) + throws IOException; +} diff --git a/source/java/org/alfresco/repo/importer/AVMZipBootstrap.java b/source/java/org/alfresco/repo/importer/AVMZipBootstrap.java new file mode 100644 index 0000000000..8fea537db6 --- /dev/null +++ b/source/java/org/alfresco/repo/importer/AVMZipBootstrap.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.importer; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.repository.NodeService; +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.apache.tools.zip.ZipFile; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * A Bootstrap-time Importer which allows the loading + * of part of an AVM filesystem from a Zip file + * + * @author Nick Burch + */ +public class AVMZipBootstrap extends AbstractLifecycleBean +{ + // Logger + private static final Log logger = LogFactory.getLog(AVMZipBootstrap.class); + + private TransactionService transactionService; + private RetryingTransactionHelper retryingTransactionHelper; + private NodeService nodeService; + private AuthenticationContext authenticationContext; + private AVMService avmService; + private AVMZipImporter avmZipImporter; + + private boolean allowWrite = true; + private String location; + private String avmRoot; + + /** + * Set whether we write or not + * + * @param write true (default) if the import must go ahead, otherwise no import will occur + */ + public void setAllowWrite(boolean write) + { + this.allowWrite = write; + } + + /** + * Sets the Root of the AVM store to import to. + */ + public void setAvmRoot(String avmRoot) + { + this.avmRoot = avmRoot; + } + + /** + * Sets the location of the zip file to import from + */ + public void setLocation(String location) + { + this.location = location; + } + + /** + * Sets the AVM Importer to be used for importing to + * + * @param avmZipImporter The AVM Importer Service + */ + public void setAvmZipImporter(AVMZipImporter avmZipImporter) + { + this.avmZipImporter = avmZipImporter; + } + + /** + * Sets the AVM Service to be used for exporting from + * + * @param avmService The AVM Service + */ + public void setAvmService(AVMService avmService) + { + this.avmService = avmService; + } + + /** + * Sets the Transaction Service + * + * @param userTransaction the transaction service + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Sets the retrying transaction helper specific to the importer bootstrap. This transaction helper's parameters are + * tuned to the longer-running import transaction. + * + * @param retryingTransactionHelper + * the retrying transaction helper + */ + public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper) + { + this.retryingTransactionHelper = retryingTransactionHelper; + } + + /** + * Sets the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the authentication component + * + * @param authenticationContext + */ + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + /** + * Bootstrap the Repository + */ + public void bootstrap() + { + PropertyCheck.mandatory(this, "avmZipImporter", avmZipImporter); + PropertyCheck.mandatory(this, "retryingTransactionHelper", retryingTransactionHelper); + PropertyCheck.mandatory(this, "nodeService", nodeService); + + if (avmRoot == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No AVM Root URL - bootstrap import ignored"); + } + return; + } + + if (location == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No Location given to import from - bootstrap import ignored"); + } + return; + } + + try + { + // import the content - note: in MT case, this will run in System context of tenant domain + RunAsWork importRunAs = new RunAsWork() + { + public Object doWork() throws Exception + { + RetryingTransactionCallback doImportCallback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + doImport(); + return null; + } + }; + return retryingTransactionHelper.doInTransaction(doImportCallback, transactionService.isReadOnly(), false); + } + }; + AuthenticationUtil.runAs(importRunAs, authenticationContext.getSystemUserName()); + } + catch(Throwable e) + { + throw new AlfrescoRuntimeException("Bootstrap failed", e); + } + } + + /** + * Perform the actual import work. This is just separated to allow for simpler TXN demarcation. + */ + private void doImport() throws Throwable + { + if (!allowWrite) + { + // we're in read-only node + logger.warn("Skipping import as read-only: " + avmRoot); + } + else + { + // Create the store if necessary + String store = avmRoot.substring(0, avmRoot.indexOf(':')); + if(avmService.getStore(store) == null) + { + avmService.createStore(store); + } + + logger.debug("Bootstrapping AVM data from " + location + " to " + avmRoot); + + // Open the Zip + ZipFile zip = new ZipFile( + ImporterBootstrap.getFile(location) + ); + + // Now do the import + avmZipImporter.importNodes(zip, avmRoot); + } + } + + @Override + protected void onBootstrap(ApplicationEvent event) + { + bootstrap(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // NOOP + } +} diff --git a/source/java/org/alfresco/repo/importer/AVMZipImporter.java b/source/java/org/alfresco/repo/importer/AVMZipImporter.java new file mode 100644 index 0000000000..7936b05276 --- /dev/null +++ b/source/java/org/alfresco/repo/importer/AVMZipImporter.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.importer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.zip.ZipException; + +import org.alfresco.service.cmr.avm.AVMExistsException; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMNotFoundException; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avm.AVMStoreDescriptor; +import org.alfresco.service.cmr.avm.AVMWrongTypeException; +import org.apache.poi.util.IOUtils; +import org.apache.tools.zip.ZipEntry; +import org.apache.tools.zip.ZipFile; + +/** + * Importer which allows the loading of part of an AVM + * filesystem from a Zip file, typically called at + * boostrap time. + * + * @author Nick Burch + */ +public class AVMZipImporter implements AVMImporter +{ + private AVMService avmService; + + /** + * Sets the AVM Service to be used for importing to + * + * @param avmService The AVM Service + */ + public void setAvmService(AVMService avmService) + { + this.avmService = avmService; + } + + /** + * Imports all the entries in the Zip File into + * the AVM store. + * @param storePath The full store path, eg stores:/alfresco/foo/ + */ + public void importNodes(ZipFile zip, String storePath) throws IOException, ZipException + { + String store = storePath; + String path = null; + + int splitAt = storePath.indexOf(':'); + if(splitAt != -1) { + store = storePath.substring(0, splitAt); + path = storePath.substring(splitAt+1); + } + + AVMStoreDescriptor avmStore = avmService.getStore(store); + if(avmStore == null) + { + throw new IllegalArgumentException("No AVM store found with name " + store); + } + + importNodes(zip, avmStore, path); + } + + /** + * Imports all the entries in the Zip File into + * the AVM store. + * @param avmStore The AVM Store to import into + * @param storePath Where in the AVM store to unpack into + */ + @SuppressWarnings("unchecked") + public void importNodes(ZipFile zip, AVMStoreDescriptor avmStore, String storePath) throws IOException, ZipException + { + if(avmStore == null) + { + throw new IllegalArgumentException("An AVM Store must be supplied"); + } + + // Normalise the store path + if(storePath == null) { + storePath = "/"; + } + if(! storePath.startsWith("/")) { + storePath = "/" + storePath; + } + if(! storePath.endsWith("/")) { + storePath = storePath + "/"; + } + + // Process the zip file + Enumeration entries = zip.getEntries(); + while(entries.hasMoreElements()) + { + // Grab the entry, and build the AVM path for it + ZipEntry entry = entries.nextElement(); + String relativeName = entry.getName(); + if(relativeName.startsWith("/")) { + relativeName = relativeName.substring(1); + } + String avmName = storePath + relativeName; + + // Import in the appropriate way for the file + if(entry.isDirectory()) + { + ensureDirectory(avmStore, avmName); + } + else + { + String dir = avmName.substring(0, avmName.lastIndexOf('/') + 1); + ensureDirectory(avmStore, dir); + + String avmPath = avmStore.getName() + ":" + avmName; + importNode(zip.getInputStream(entry), avmPath); + } + } + } + + /** + * Recursively creates directories in AVM as required + */ + protected void ensureDirectory(AVMStoreDescriptor store, String name) + { + if(! name.endsWith("/")) { + throw new IllegalArgumentException(name + " isn't a directory"); + } else { + name = name.substring(0, name.length()-1); + } + if(! name.startsWith("/")) { + // Make it absolute + name = "/" + name; + } + if(name.equals("/")) { + throw new IllegalArgumentException("Can't create a store"); + } + + // Build up the AVM path + int splitAt = name.lastIndexOf('/'); + String avmPath = store.getName() + ":" + name.substring(0, splitAt); + String avmName = name.substring(splitAt+1); + + try { + avmService.createDirectory(avmPath, avmName); + } catch(AVMNotFoundException nfe) { + // Recurse + String parent = name.substring(0, splitAt+1); + ensureDirectory(store, parent); + // And re-do + avmService.createDirectory(avmPath, avmName); + } catch(AVMExistsException exists) { + // Already there + } catch(AVMWrongTypeException wte) { + // Currently there, but not a directory... + throw wte; + } + } + + /** + * Imports the given path and version from the source data. + * + * @param input The stream to read from + * @param path The AVM path to import + */ + public void importNode(InputStream input, String path) + throws IOException + { + try + { + OutputStream out = avmService.getFileOutputStream(path); + IOUtils.copy(input, out); + } + catch(AVMNotFoundException e) + { + int splitAt = path.lastIndexOf('/'); + String avmPath = path.substring(0, splitAt); + String avmName = path.substring(splitAt+1); + avmService.createFile(avmPath, avmName, input); + } + } + + /** + * Imports the given AVM node from the source data. + * + * @param input The stream to read from + * @param node The AVM node to import + */ + public void importNode(InputStream input, AVMNodeDescriptor node) + throws IOException + { + OutputStream out = avmService.getFileOutputStream(node.getPath()); + IOUtils.copy(input, out); + } +} diff --git a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java index 1a318166d9..cdfb3ba7ec 100644 --- a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java +++ b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java @@ -527,7 +527,7 @@ public class ImporterBootstrap extends AbstractLifecycleBean * @param view the view location * @return the file */ - private File getFile(String view) + public static File getFile(String view) { // Try as a file location File file = new File(view); @@ -540,7 +540,7 @@ public class ImporterBootstrap extends AbstractLifecycleBean // Try as a classpath location // Get input stream - InputStream viewStream = getClass().getClassLoader().getResourceAsStream(view); + InputStream viewStream = ImporterBootstrap.class.getClassLoader().getResourceAsStream(view); if (viewStream == null) { throw new ImporterException("Could not find view file " + view); diff --git a/source/java/org/alfresco/repo/importer/ImporterComponent.java b/source/java/org/alfresco/repo/importer/ImporterComponent.java index c953889c10..40c772a29f 100644 --- a/source/java/org/alfresco/repo/importer/ImporterComponent.java +++ b/source/java/org/alfresco/repo/importer/ImporterComponent.java @@ -35,6 +35,10 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.importer.view.NodeContext; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionListener; +import org.alfresco.repo.version.Version2Model; +import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; @@ -57,6 +61,8 @@ import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.view.ImportPackageHandler; import org.alfresco.service.cmr.view.ImporterBinding; import org.alfresco.service.cmr.view.ImporterException; @@ -68,6 +74,7 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.hibernate.engine.TransactionHelper; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -103,6 +110,12 @@ public class ImporterComponent private AuthorityService authorityService; private AuthenticationContext authenticationContext; private OwnableService ownableService; + private VersionService versionService; + + /** + * The db node service, used when updating the version store. + */ + protected NodeService dbNodeService; // binding markers private static final String START_BINDING_MARKER = "${"; @@ -207,6 +220,24 @@ public class ImporterComponent this.ownableService = ownableService; } + /** + * @param versionService versionService + */ + public void setVersionService(VersionService versionService) + { + this.versionService = versionService; + } + + /** + * Sets the db node service, used when updating the + * versioning information + * + * @param nodeService the node service + */ + public void setDbNodeService(NodeService nodeService) + { + this.dbNodeService = nodeService; + } /* (non-Javadoc) * @see org.alfresco.service.cmr.view.ImporterService#importView(java.io.InputStreamReader, org.alfresco.service.cmr.view.Location, java.util.Properties, org.alfresco.service.cmr.view.ImporterProgress) @@ -461,7 +492,7 @@ public class ImporterComponent // initialise list of content models to exclude from import if (binding == null || binding.getExcludedClasses() == null) { - this.excludedClasses = new QName[] { ContentModel.ASPECT_REFERENCEABLE, ContentModel.ASPECT_VERSIONABLE }; + this.excludedClasses = new QName[] { ContentModel.ASPECT_REFERENCEABLE }; } else { @@ -606,8 +637,49 @@ public class ImporterComponent } } + // if the node has the versionable aspect applied to it, + // create an initial version for it + if(context.getNodeAspects().contains(ContentModel.ASPECT_VERSIONABLE)) + { + generateVersioningForVersionableNode(nodeRef); + } + return nodeRef; } + + /** + * Fixes things up for versionable nodes after importing. + * Because version information is stored in a different store, + * the past versions are not included in the ACP. + * However, because the node has the versionable aspect applied to + * it, we still need it to have a single version in the version store. + * This method arranges for that. + */ + private void generateVersioningForVersionableNode(final NodeRef nodeRef) + { + // Is versioning already turned on? + if(versionService.getVersionHistory(nodeRef) != null) + { + // There is already version history, so we don't need to do anything + return; + } + + // Take a copy of the version label, as it'll be reset when + // we request that versioning occurs + final String label = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + + // Have versioning enabled + Version version = versionService.createVersion(nodeRef, null); + final NodeRef versionNodeRef = VersionUtil.convertNodeRef(version.getFrozenStateNodeRef()); + + // Put the version label back how it should be on the main node + dbNodeService.setProperty(nodeRef, ContentModel.PROP_VERSION_LABEL, label); + + // Fix up the versioned version node to be what it should be + // (The previous version label should be off, and the current label is the new one) + dbNodeService.setProperty(versionNodeRef, ContentModel.PROP_VERSION_LABEL, null); + dbNodeService.setProperty(versionNodeRef, Version2Model.PROP_QNAME_VERSION_LABEL, label); + } /** * Link an existing Node @@ -1510,6 +1582,12 @@ public class ImporterComponent permissionService.setPermission(existingNodeRef, permission.getAuthority(), permission.getPermission(), permission.getAccessStatus().equals(AccessStatus.ALLOWED)); } } + + if(logger.isDebugEnabled()) + { + logger.debug("Updating existing node " + existingNodeRef + " at " + + nodeService.getPath(existingNodeRef) + " for " + node.toString()); + } // report update reportPropertySet(existingNodeRef, updateProperties); diff --git a/source/java/org/alfresco/repo/importer/ImporterComponentTest.java b/source/java/org/alfresco/repo/importer/ImporterComponentTest.java index 8dea133067..34fccba95f 100644 --- a/source/java/org/alfresco/repo/importer/ImporterComponentTest.java +++ b/source/java/org/alfresco/repo/importer/ImporterComponentTest.java @@ -33,6 +33,8 @@ 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.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.view.ImporterService; import org.alfresco.service.cmr.view.Location; import org.alfresco.service.cmr.view.ImporterBinding.UUID_BINDING; @@ -48,6 +50,7 @@ public class ImporterComponentTest extends BaseSpringTest { private ImporterService importerService; private ImporterBootstrap importerBootstrap; + private VersionService versionService; private NodeService nodeService; private StoreRef storeRef; private AuthenticationComponent authenticationComponent; @@ -65,6 +68,8 @@ public class ImporterComponentTest extends BaseSpringTest this.authenticationComponent.setSystemUserAsCurrentUser(); + this.versionService = (VersionService)this.applicationContext.getBean("VersionService"); + // Create the store this.storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); } @@ -136,6 +141,51 @@ public class ImporterComponentTest extends BaseSpringTest assertEquals("cm:modifier not preserved during import", AuthenticationUtil.getSystemUserName(), modifier); } + public void testImportWithVersioning() throws Exception + { + InputStream test = getClass().getClassLoader().getResourceAsStream("org/alfresco/repo/importer/importercomponent_test.xml"); + InputStreamReader testReader = new InputStreamReader(test, "UTF-8"); + Location location = new Location(storeRef); + try + { + importerService.importView( + testReader, + location, + null, + new ImportTimerProgress()); + } + finally + { + testReader.close(); + } + + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + List childAssocs = nodeService.getChildAssocs( + rootNodeRef, + RegexQNamePattern.MATCH_ALL, + new RegexQNamePattern(NamespaceService.CONTENT_MODEL_1_0_URI, "Version Containing Folder")); + assertEquals("'Version Folder' path not found", 1, childAssocs.size()); + NodeRef versionFolder = childAssocs.get(0).getChildRef(); + + childAssocs = nodeService.getChildAssocs( + versionFolder, + RegexQNamePattern.MATCH_ALL, + new RegexQNamePattern(NamespaceService.CONTENT_MODEL_1_0_URI, "Versioned Node")); + assertEquals("'Versioned Node' path not found", 1, childAssocs.size()); + NodeRef versionedNode = childAssocs.get(0).getChildRef(); + + // Check the version label isn't 1.0, but the 1.15 from the ACP + assertEquals("1.15", nodeService.getProperty(versionedNode, ContentModel.PROP_VERSION_LABEL)); + + // Check that there's no history on the (un-versioned) folder + assertEquals(null, versionService.getVersionHistory(versionFolder)); + + // Check that there's a single version history entry for the node + VersionHistory vh = versionService.getVersionHistory(versionedNode); + assertNotNull(vh); + assertEquals(1, vh.getAllVersions().size()); + } + public void testImportWithUuidBinding() throws Exception { Location location = new Location(storeRef); diff --git a/source/java/org/alfresco/repo/importer/importercomponent_test.xml b/source/java/org/alfresco/repo/importer/importercomponent_test.xml index fef8f73c56..53ba930c42 100644 --- a/source/java/org/alfresco/repo/importer/importercomponent_test.xml +++ b/source/java/org/alfresco/repo/importer/importercomponent_test.xml @@ -117,6 +117,30 @@ + + + + + Version Containing Folder + + + + + + + + + Versioned Node + true + true + 1.15 + true + + + + + + @@ -127,4 +151,4 @@ - \ No newline at end of file + diff --git a/source/java/org/alfresco/repo/invitation/InvitationServiceImplTest.java b/source/java/org/alfresco/repo/invitation/InvitationServiceImplTest.java index 45d9b7e8da..00ca2410cb 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationServiceImplTest.java +++ b/source/java/org/alfresco/repo/invitation/InvitationServiceImplTest.java @@ -246,7 +246,7 @@ public class InvitationServiceImplTest extends BaseAlfrescoSpringTest assertNull("Not been sent yet", msg.getReceivedDate()); // TODO - check some more details of the email - assertEquals("Invitation to join InviteSiteTitle site", msg.getSubject()); + assertTrue((msg.getSubject().indexOf("You have been invited to join the") != -1)); } /** diff --git a/source/java/org/alfresco/repo/invitation/ModeratedActionReject.java b/source/java/org/alfresco/repo/invitation/ModeratedActionReject.java index 4c6f825264..f3c0bc3bb3 100644 --- a/source/java/org/alfresco/repo/invitation/ModeratedActionReject.java +++ b/source/java/org/alfresco/repo/invitation/ModeratedActionReject.java @@ -19,6 +19,7 @@ package org.alfresco.repo.invitation; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; @@ -35,6 +36,9 @@ import org.springframework.beans.factory.BeanFactory; /** * JBPM Action fired when a moderated invitation is rejected. + * Note - uses a classpath template, rather than a data dictionary template, + * so behaves slightly differently to many other mail actions, and can't + * currently be localised easily. */ public class ModeratedActionReject extends JBPMSpringActionHandler { @@ -43,7 +47,7 @@ public class ModeratedActionReject extends JBPMSpringActionHandler private ActionService actionService; private TemplateService templateService; - //private String rejectTemplate = " PATH:\"app:company_home/app:dictionary/app:email_templates/cm:invite/cm:moderated-reject-email.ftl\""; +// private String rejectTemplate = " PATH:\"app:company_home/app:dictionary/app:email_templates/cm:invite/cm:moderated-reject-email.ftl\""; private String rejectTemplate = "/alfresco/bootstrap/invite/moderated-reject-email.ftl"; private boolean sendEmails = true; @@ -81,16 +85,21 @@ public class ModeratedActionReject extends JBPMSpringActionHandler // send email to the invitee if possible - but don't fail the rejection if email cannot be sent try { - Map model = new HashMap(8, 1.0f); + // Build our model + Map model = new HashMap(8, 1.0f); model.put("resourceName", resourceName); model.put("resourceType", resourceType); model.put("inviteeRole", inviteeRole); model.put("reviewComments", reviewComments); model.put("reviewer", reviewer); model.put("inviteeUserName", inviteeUserName); - + + // Process the template + // Note - because we use a classpath template, rather than a Data Dictionary + // one, we can't have the MailActionExecutor do the template for us String emailMsg = templateService.processTemplate("freemarker", rejectTemplate, model); + // Send Action emailAction = actionService.createAction("mail"); emailAction.setParameterValue(MailActionExecuter.PARAM_TO, inviteeUserName); emailAction.setParameterValue(MailActionExecuter.PARAM_FROM, reviewer); diff --git a/source/java/org/alfresco/repo/invitation/site/InviteSender.java b/source/java/org/alfresco/repo/invitation/site/InviteSender.java index 8037160f26..d510e7658b 100644 --- a/source/java/org/alfresco/repo/invitation/site/InviteSender.java +++ b/source/java/org/alfresco/repo/invitation/site/InviteSender.java @@ -19,8 +19,17 @@ package org.alfresco.repo.invitation.site; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.*; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarAcceptUrl; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarInviteTicket; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarInviteeGenPassword; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarInviteeUserName; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarInviterUserName; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarRejectUrl; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarResourceName; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarRole; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarServerPath; +import java.io.Serializable; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; @@ -30,13 +39,16 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.model.Repository; import org.alfresco.repo.search.SearcherException; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.invitation.InvitationException; +import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -47,6 +59,8 @@ import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.util.ModelUtil; +import org.alfresco.util.UrlUtil; import org.springframework.extensions.surf.util.ParameterCheck; import org.springframework.extensions.surf.util.URLEncoder; @@ -79,9 +93,11 @@ public class InviteSender private final PersonService personService; private final SearchService searchService; private final SiteService siteService; - private final TemplateService templateService; private final Repository repository; private final MessageService messageService; + private final FileFolderService fileFolderService; + private final SysAdminParams sysAdminParams; + private final RepoAdminService repoAdminService; public InviteSender(ServiceRegistry services, Repository repository, MessageService messageService) { @@ -90,7 +106,9 @@ public class InviteSender this.personService = services.getPersonService(); this.searchService = services.getSearchService(); this.siteService = services.getSiteService(); - this.templateService = services.getTemplateService(); + this.fileFolderService = services.getFileFolderService(); + this.sysAdminParams = services.getSysAdminParams(); + this.repoAdminService = services.getRepoAdminService(); this.repository = repository; this.messageService = messageService; } @@ -112,8 +130,9 @@ public class InviteSender mail.setParameterValue(MailActionExecuter.PARAM_FROM, getEmail(inviter)); mail.setParameterValue(MailActionExecuter.PARAM_TO, getEmail(invitee)); mail.setParameterValue(MailActionExecuter.PARAM_SUBJECT, buildSubject(properties)); - String mailText = buildMailText(properties, inviter, invitee); - mail.setParameterValue(MailActionExecuter.PARAM_TEXT, mailText); + mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, getEmailTemplateNodeRef()); + mail.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, + (Serializable)buildMailTextModel(properties, inviter, invitee)); actionService.executeAction(mail, getWorkflowPackage(properties)); } @@ -133,22 +152,26 @@ public class InviteSender private String buildSubject(Map properties) { - return messageService.getMessage("invitation.invitesender.email.subject", getSiteName(properties)); + return messageService.getMessage("invitation.invitesender.email.subject", + ModelUtil.getProductName(repoAdminService), getSiteName(properties)); } - private String buildMailText(Map properties, NodeRef inviter, NodeRef invitee) + private Map buildMailTextModel(Map properties, NodeRef inviter, NodeRef invitee) { - String template = getEmailTemplate(); - Map model = makeDefaultModel(); + // Set the core model parts + // Note - the user part is skipped, as that's implied via the run-as + Map model = new HashMap(); + model.put(TemplateService.KEY_COMPANY_HOME, repository.getCompanyHome()); + model.put(TemplateService.KEY_USER_HOME, repository.getUserHome(repository.getPerson())); + model.put(TemplateService.KEY_SHARE_URL, UrlUtil.getShareUrl(sysAdminParams) + "/"); + model.put(TemplateService.KEY_PRODUCT_NAME, ModelUtil.getProductName(repoAdminService)); + + // Build up the args for rendering inside the template Map args = buildArgs(properties, inviter, invitee); - model.put("args", args); - return templateService.processTemplate(template, model); - } - - private String getEmailTemplate() - { - NodeRef template = getEmailTemplateNodeRef(); - return template.toString(); + model.put("args", (Serializable)args); + + // All done + return model; } private Map buildArgs(Map properties, NodeRef inviter, NodeRef invitee) @@ -180,15 +203,6 @@ public class InviteSender return role; } - private Map makeDefaultModel() - { - NodeRef person = repository.getPerson(); - NodeRef companyHome = repository.getCompanyHome(); - NodeRef userHome = repository.getUserHome(person); - Map model = templateService.buildDefaultModel(person, companyHome, userHome, null, null); - return model; - } - private String getEmail(NodeRef person) { return (String) nodeService.getProperty(person, ContentModel.PROP_EMAIL); @@ -203,7 +217,7 @@ public class InviteSender private NodeRef getEmailTemplateNodeRef() { StoreRef spacesStore = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); - String query = " PATH:\"app:company_home/app:dictionary/app:email_templates/cm:invite/cm:invite-email.ftl\""; + String query = " PATH:\"app:company_home/app:dictionary/app:email_templates/cm:invite/cm:invite-email.html.ftl\""; SearchParameters searchParams = new SearchParameters(); searchParams.addStore(spacesStore); @@ -215,14 +229,18 @@ public class InviteSender { results = searchService.query(searchParams); List nodeRefs = results.getNodeRefs(); - if (nodeRefs.size() == 1) - return nodeRefs.get(0); + if (nodeRefs.size() == 1) { + // Now localise this + NodeRef base = nodeRefs.get(0); + NodeRef local = fileFolderService.getLocalizedSibling(base); + return local; + } else - throw new InvitationException("Cannot find the email templatte!"); + throw new InvitationException("Cannot find the email template!"); } catch (SearcherException e) { - throw new InvitationException("Cannot find the email templatte!", e); + throw new InvitationException("Cannot find the email template!", e); } finally { diff --git a/source/java/org/alfresco/repo/invitation/site/InviteSenderTest.java b/source/java/org/alfresco/repo/invitation/site/InviteSenderTest.java index 3fc1a01d63..55353bbc2c 100644 --- a/source/java/org/alfresco/repo/invitation/site/InviteSenderTest.java +++ b/source/java/org/alfresco/repo/invitation/site/InviteSenderTest.java @@ -19,9 +19,22 @@ package org.alfresco.repo.invitation.site; -import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarAcceptUrl; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarInviteTicket; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarInviteeGenPassword; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarInviteeUserName; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarInviterUserName; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarRejectUrl; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarResourceName; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarRole; +import static org.alfresco.repo.invitation.WorkflowModelNominatedInvitation.wfVarServerPath; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.io.Serializable; import java.util.Arrays; @@ -33,21 +46,27 @@ import junit.framework.TestCase; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.admin.SysAdminParamsImpl; import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.model.Repository; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.admin.RepoUsage; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; +import org.alfresco.service.cmr.model.FileFolderService; 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.repository.TemplateService; 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.security.PersonService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.util.ModelUtil; import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -81,10 +100,10 @@ public class InviteSenderTest extends TestCase private static final String instanceId = "InstanceId"; private final MessageService messageService = mock(MessageService.class); - private Action mailAction = mock(Action.class); + private Action mailAction; private SiteInfo siteInfo = mock(SiteInfo.class); - private TemplateService templateService; private InviteSender sender; + private Map lastSetMailModel; public void testSendMailWorkingPath() throws Exception { @@ -92,20 +111,31 @@ public class InviteSenderTest extends TestCase String subjectPropertyName = "invitation.invitesender.email.subject"; String subjectMsg = "Subject message"; - when(messageService.getMessage(eq(subjectPropertyName), any())).thenReturn(subjectMsg); + when(messageService.getMessage(eq(subjectPropertyName), eq("Share"), eq(siteShortName))).thenReturn(subjectMsg); Map properties = buildDefaultProperties(); sender.sendMail(properties); - verify(messageService).getMessage(eq(subjectPropertyName), any()); + verify(messageService).getMessage(eq(subjectPropertyName), eq("Share"), eq(siteShortName)); verify(messageService).getMessage(eq(rolePropertyName)); verify(mailAction).setParameterValue(eq(MailActionExecuter.PARAM_FROM), eq(inviter.email)); verify(mailAction).setParameterValue(eq(MailActionExecuter.PARAM_TO), eq(invitee.email)); verify(mailAction).setParameterValue(eq(MailActionExecuter.PARAM_SUBJECT), eq(subjectMsg)); + verify(mailAction).setParameterValue(eq(MailActionExecuter.PARAM_TEMPLATE), (Serializable)any()); - Map argsMap = getArgsMap(); + ArgumentCaptor modelC = ArgumentCaptor.forClass(Map.class); + verify(mailAction).setParameterValue(eq(MailActionExecuter.PARAM_TEMPLATE_MODEL), (Serializable)modelC.capture()); + + // Check the model + Map model = modelC.getValue(); + assertNotNull(model); + assertEquals(null, model.get("userhome")); + assertNotNull(model.get("shareUrl")); + + // And the args within it + Map argsMap = (Map)model.get("args"); assertNotNull(argsMap); assertEquals(siteShortName, argsMap.get("siteName")); assertEquals(invitee.node.toString(), argsMap.get("inviteePersonRef")); @@ -120,15 +150,26 @@ public class InviteSenderTest extends TestCase "test://test/path/reject?inviteId=InstanceId&inviteeUserName=invitee&siteShortName=Full Site Name&inviteTicket=Ticket", argsMap.get("rejectLink")); + // When no role message is found then the role name is used. assertEquals(role, argsMap.get("inviteeSiteRole")); - reset(templateService, messageService); + + // Check that when the role message is set then that role message is used. + reset(mailAction, messageService); String roleMsg = "role message"; when(messageService.getMessage(rolePropertyName)).thenReturn(roleMsg); sender.sendMail(properties); - // Check that when the role message is set then that role message is used. - assertEquals(roleMsg, getArgsMap().get("inviteeSiteRole")); + + // Grab the args and check + modelC = ArgumentCaptor.forClass(Map.class); + verify(mailAction).setParameterValue(eq(MailActionExecuter.PARAM_TEMPLATE_MODEL), (Serializable)modelC.capture()); + model = modelC.getValue(); + assertNotNull(model); + argsMap = (Map)model.get("args"); + assertNotNull(argsMap); + + assertEquals(roleMsg, argsMap.get("inviteeSiteRole")); } public void testSendMailWithWhitespaceUserName() throws Exception @@ -136,7 +177,18 @@ public class InviteSenderTest extends TestCase Map properties = buildDefaultProperties(); properties.put(wfVarInviteeUserName, whitespaceInvitee.name); sender.sendMail(properties); - Map argsMap = getArgsMap(); + + ArgumentCaptor modelC = ArgumentCaptor.forClass(Map.class); + verify(mailAction).setParameterValue(eq(MailActionExecuter.PARAM_TEMPLATE_MODEL), (Serializable)modelC.capture()); + + // Check the model + Map model = modelC.getValue(); + assertNotNull(model); + assertEquals(null, model.get("userhome")); + assertNotNull(model.get("shareUrl")); + + // And the args within it + Map argsMap = (Map)model.get("args"); String acceptLink = argsMap.get("acceptLink"); assertEquals( "test://test/path/accpet?inviteId=InstanceId&inviteeUserName=First%20Second%09third%0aFourth%0d%0aFifth&siteShortName=Full Site Name&inviteTicket=Ticket", @@ -152,7 +204,18 @@ public class InviteSenderTest extends TestCase Map properties = buildDefaultProperties(); properties.put(wfVarInviteeUserName, specialCharInvitee.name); sender.sendMail(properties); - Map argsMap = getArgsMap(); + + ArgumentCaptor modelC = ArgumentCaptor.forClass(Map.class); + verify(mailAction).setParameterValue(eq(MailActionExecuter.PARAM_TEMPLATE_MODEL), (Serializable)modelC.capture()); + + // Check the model + Map model = modelC.getValue(); + assertNotNull(model); + assertEquals(null, model.get("userhome")); + assertNotNull(model.get("shareUrl")); + + // And the args within it + Map argsMap = (Map)model.get("args"); String acceptLink = argsMap.get("acceptLink"); assertEquals( "test://test/path/accpet?inviteId=InstanceId&inviteeUserName=%c3%a0%c3%a2%c3%a6%c3%a7%c3%a9%c3%a8%c3%aa%c3%ab%c3%ae%c3%af%c3%b4%c5%93%c3%b9%c3%bb%c3%bc%c3%bf%c3%b1&siteShortName=Full Site Name&inviteTicket=Ticket", @@ -162,7 +225,7 @@ public class InviteSenderTest extends TestCase "test://test/path/reject?inviteId=InstanceId&inviteeUserName=%c3%a0%c3%a2%c3%a6%c3%a7%c3%a9%c3%a8%c3%aa%c3%ab%c3%ae%c3%af%c3%b4%c5%93%c3%b9%c3%bb%c3%bc%c3%bf%c3%b1&siteShortName=Full Site Name&inviteTicket=Ticket", rejectLink); } - + private Map buildDefaultProperties() { Map properties = new HashMap(); @@ -180,16 +243,6 @@ public class InviteSenderTest extends TestCase return properties; } - @SuppressWarnings("unchecked") - private Map getArgsMap() - { - ArgumentCaptor modelCaptor = ArgumentCaptor.forClass(Map.class); - verify(templateService).processTemplate(eq(template.toString()), modelCaptor.capture()); - Map model = modelCaptor.getValue(); - Map argsMap = (Map) model.get("args"); - return argsMap; - } - @Override protected void setUp() throws Exception { @@ -197,6 +250,7 @@ public class InviteSenderTest extends TestCase ServiceRegistry services = mockServices(); Repository repository = mockRepository(); sender = new InviteSender(services, repository, messageService); + lastSetMailModel = null; } /** @@ -221,7 +275,9 @@ public class InviteSenderTest extends TestCase PersonService mockPersonService = mockPersonService(); SearchService mockSearchService = mockSearchService(); SiteService mockSiteService = mockSiteService(); - TemplateService mockTemplateService = mockTemplateService(); + FileFolderService mockFileFolderService = mockFileFolderService(); + RepoAdminService mockRepoAdminService = mockRepoAdminService(); + SysAdminParams sysAdminParams = new SysAdminParamsImpl(); ServiceRegistry services = mock(ServiceRegistry.class); when(services.getActionService()).thenReturn(mockActionService); @@ -229,29 +285,41 @@ public class InviteSenderTest extends TestCase when(services.getPersonService()).thenReturn(mockPersonService); when(services.getSearchService()).thenReturn(mockSearchService); when(services.getSiteService()).thenReturn(mockSiteService); - when(services.getTemplateService()).thenReturn(mockTemplateService); + when(services.getFileFolderService()).thenReturn(mockFileFolderService); + when(services.getSysAdminParams()).thenReturn(sysAdminParams); + when(services.getRepoAdminService()).thenReturn(mockRepoAdminService); return services; } - + /** - * Mocks up a TemplateService that returns an empty HashMap when - * buildDefaultModel() is called. - * - * @return + * Mocks up a FileFolderService that claims there are + * no localised templates available */ - private TemplateService mockTemplateService() + private FileFolderService mockFileFolderService() { - this.templateService = mock(TemplateService.class); - when(templateService.buildDefaultModel(inviter.node, null, null, null, null)).thenAnswer( - new Answer>() + FileFolderService fileFolderService = mock(FileFolderService.class); + when(fileFolderService.getLocalizedSibling( (NodeRef)null )).thenAnswer( + new Answer() + { + public NodeRef answer(InvocationOnMock invocation) throws Throwable { - public Map answer(InvocationOnMock invocation) throws Throwable - { - return new HashMap(); - } - }); - when(templateService.processTemplate(anyString(), any())).thenReturn(mailText); - return templateService; + Object[] o = invocation.getArguments(); + if(o == null || o.length == 0) return null; + return (NodeRef)o[0]; + } + } + ); + return fileFolderService; + } + + private RepoAdminService mockRepoAdminService() + { + RepoUsage usage = new RepoUsage(System.currentTimeMillis(), 10l, 100l, + LicenseMode.ENTERPRISE, System.currentTimeMillis(), false); + + RepoAdminService repoAdminService = mock(RepoAdminService.class); + when(repoAdminService.getRestrictions()).thenReturn(usage); + return repoAdminService; } /** @@ -324,6 +392,8 @@ public class InviteSenderTest extends TestCase */ private ActionService mockActionService() { + mailAction = mock(Action.class); + ActionService actionService = mock(ActionService.class); when(actionService.createAction(MailActionExecuter.NAME)).thenReturn(mailAction); return actionService; diff --git a/source/java/org/alfresco/repo/jgroups/AlfrescoJGroupsChannelFactory.java b/source/java/org/alfresco/repo/jgroups/AlfrescoJGroupsChannelFactory.java index 086096d04c..f099815085 100644 --- a/source/java/org/alfresco/repo/jgroups/AlfrescoJGroupsChannelFactory.java +++ b/source/java/org/alfresco/repo/jgroups/AlfrescoJGroupsChannelFactory.java @@ -31,7 +31,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.alfresco.error.AlfrescoRuntimeException; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -51,6 +50,7 @@ import org.jgroups.View; import org.jgroups.protocols.LOOPBACK; import org.jgroups.stack.ProtocolStack; import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; import org.springframework.util.ResourceUtils; /** @@ -718,7 +718,7 @@ public class AlfrescoJGroupsChannelFactory extends AbstractLifecycleBean } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) public Map dumpStats() { return delegate.dumpStats(); @@ -736,8 +736,8 @@ public class AlfrescoJGroupsChannelFactory extends AbstractLifecycleBean return delegate.flushSupported(); } + @SuppressWarnings("rawtypes") @Override - @SuppressWarnings("unchecked") public boolean getAllStates(Vector targets, long timeout) throws ChannelNotConnectedException, ChannelClosedException { return delegate.getAllStates(targets, timeout); @@ -923,5 +923,29 @@ public class AlfrescoJGroupsChannelFactory extends AbstractLifecycleBean return delegate.toString(); } } + + @Override + public String getName(Address member) + { + return delegate.getName(member); + } + + @Override + public void send(Address dst, Address src, byte[] buf) throws ChannelNotConnectedException, ChannelClosedException + { + delegate.send(dst, src, buf); + } + + @Override + public void send(Address dst, Address src, byte[] buf, int offset, int length) throws ChannelNotConnectedException, ChannelClosedException + { + delegate.send(dst, src, buf, offset, length); + } + + @Override + public void setName(String name) + { + delegate.setName(name); + } } } diff --git a/source/java/org/alfresco/repo/jscript/People.java b/source/java/org/alfresco/repo/jscript/People.java index a856cf64db..7a803c646c 100644 --- a/source/java/org/alfresco/repo/jscript/People.java +++ b/source/java/org/alfresco/repo/jscript/People.java @@ -24,12 +24,13 @@ import java.util.Set; import java.util.StringTokenizer; import org.alfresco.model.ContentModel; -import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.UserNameGenerator; import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.repo.security.person.PersonServiceImpl; import org.alfresco.repo.security.sync.UserRegistrySynchronizer; +import org.alfresco.repo.tenant.TenantDomainMismatchException; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; @@ -43,9 +44,7 @@ import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.usage.ContentUsageService; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.springframework.extensions.surf.util.ParameterCheck; import org.alfresco.util.PropertyMap; import org.alfresco.util.ValueDerivingMapFactory; import org.alfresco.util.ValueDerivingMapFactory.ValueDeriver; @@ -54,6 +53,7 @@ import org.apache.commons.logging.LogFactory; import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; import org.springframework.beans.factory.InitializingBean; +import org.springframework.extensions.surf.util.ParameterCheck; /** * Scripted People service for describing and executing actions against People & Groups. @@ -232,7 +232,8 @@ public final class People extends BaseScopableProcessorExtension implements Init } /** - * Create a Person with an optionally generated user name + * Create a Person with an optionally generated user name. + * This version doesn't notify them. * * @param userName userName or null for a generated user name * @param firstName firstName @@ -246,6 +247,30 @@ public final class People extends BaseScopableProcessorExtension implements Init * could not be created */ public ScriptNode createPerson(String userName, String firstName, String lastName, String emailAddress, String password, boolean setAccountEnabled) + { + return createPerson(userName, firstName, lastName, emailAddress, password, setAccountEnabled, false); + } + + /** + * Create a Person with an optionally generated user name + * + * @param userName userName or null for a generated user name + * @param firstName firstName + * @param lastName lastName + * @param emailAddress emailAddress + * @param password if not null creates a new authenticator with the given password. + * @param setAccountEnabled + * set to 'true' to create enabled user account, or 'false' to + * create disabled user account for created person. + * @param notifyByEmail + * set to 'true' to have the new user emailed to let them know + * their account details. Only applies if a username and + * password were supplied. + * @return the person node (type cm:person) created or null if the person + * could not be created + */ + public ScriptNode createPerson(String userName, String firstName, String lastName, String emailAddress, + String password, boolean setAccountEnabled, boolean notifyByEmail) { ParameterCheck.mandatory("firstName", firstName); ParameterCheck.mandatory("lastName", lastName); @@ -270,29 +295,13 @@ public final class People extends BaseScopableProcessorExtension implements Init if (userName != null) { - if (tenantService.isEnabled()) + try { - String currentDomain = tenantService.getCurrentUserDomain(); - if (! currentDomain.equals(TenantService.DEFAULT_DOMAIN)) - { - if (! tenantService.isTenantUser(userName)) - { - // force domain onto the end of the username - userName = tenantService.getDomainUser(userName, currentDomain); - logger.warn("Added domain to username: " + userName); - } - else - { - try - { - tenantService.checkDomainUser(userName); - } - catch (RuntimeException re) - { - throw new AuthenticationException("User must belong to same domain as admin: " + currentDomain); - } - } - } + userName = PersonServiceImpl.updateUsernameForTenancy(userName, tenantService); + } + catch (TenantDomainMismatchException re) + { + throw new AuthenticationException("User must belong to same domain as admin: " + re.getTenantA()); } person = createPerson(userName, firstName, lastName, emailAddress); @@ -304,6 +313,11 @@ public final class People extends BaseScopableProcessorExtension implements Init authenticationService.setAuthenticationEnabled(userName, setAccountEnabled); person.save(); + + if(notifyByEmail) + { + personService.notifyPerson(userName, password); + } } } diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index 5d4d71a473..9db14f777d 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -46,9 +46,9 @@ import org.alfresco.repo.action.executer.TransformActionExecuter; import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; import org.alfresco.repo.search.QueryParameterDefImpl; import org.alfresco.repo.tagging.script.TagScope; -import org.alfresco.repo.thumbnail.CreateThumbnailActionExecuter; import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.repo.thumbnail.ThumbnailRegistry; +import org.alfresco.repo.thumbnail.ThumbnailHelper; import org.alfresco.repo.thumbnail.script.ScriptThumbnail; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.version.VersionModel; @@ -1826,7 +1826,7 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol { try { - this.services.getFileFolderService().move(this.nodeRef, source.getNodeRef(), destination.getNodeRef(), null); + this.services.getFileFolderService().moveFrom(this.nodeRef, source.getNodeRef(), destination.getNodeRef(), null); } catch (Exception e) { @@ -1935,6 +1935,27 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol // ------------------------------------------------------------------------------ // Checkout/Checkin Services + /** + * Ensures that this document has the cm:versionable aspect applied to it, + * and that it has the initial version in the version store. + * Calling this on a versioned node with a version store entry will have + * no effect. + * Calling this on a newly uploaded share node will have versioning enabled + * for it (Share currently does lazy versioning to improve performance of + * documents that are uploaded but never edited, and multi upload performance). + * + * @param autoVersion If the cm:versionable aspect is applied, should auto versioning be requested? + * @param autoVersionProps If the cm:versionable aspect is applied, should auto versioning of properties be requested? + */ + public void ensureVersioningEnabled(boolean autoVersion, boolean autoVersionProps) + { + Map props = new HashMap(1, 1.0f); + props.put(ContentModel.PROP_AUTO_VERSION, autoVersion); + props.put(ContentModel.PROP_AUTO_VERSION_PROPS, autoVersionProps); + + this.services.getVersionService().ensureVersioningEnabled(nodeRef, props); + } + /** * Create a version of this document. Note: this will add the cm:versionable aspect. * @@ -2480,10 +2501,10 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol } else { + Action action = ThumbnailHelper.createCreateThumbnailAction(details, services); + // Queue async creation of thumbnail - Action action = this.services.getActionService().createAction(CreateThumbnailActionExecuter.NAME); - action.setParameterValue(CreateThumbnailActionExecuter.PARAM_THUMBANIL_NAME, thumbnailName); - this.services.getActionService().executeAction(action, this.nodeRef, false, true); + this.services.getActionService().executeAction(action, this.nodeRef, true, true); } return result; diff --git a/source/java/org/alfresco/repo/jscript/Search.java b/source/java/org/alfresco/repo/jscript/Search.java index 99ee5e9e4a..293aebc043 100644 --- a/source/java/org/alfresco/repo/jscript/Search.java +++ b/source/java/org/alfresco/repo/jscript/Search.java @@ -29,7 +29,6 @@ import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.model.Repository; -import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.NodeRef; @@ -127,16 +126,11 @@ public class Search extends BaseScopableProcessorExtension public ScriptNode findNode(NodeRef ref) { ParameterCheck.mandatory("ref", ref); - String query = "ID:" + LuceneQueryParser.escape(ref.toString()); - Object[] result = query(ref.getStoreRef().toString(), query, null, SearchService.LANGUAGE_LUCENE); - if (result.length != 0) + if (this.services.getNodeService().exists(ref)) { - return (ScriptNode)result[0]; - } - else - { - return null; + return new ScriptNode(ref, this.services, getScope()); } + return null; } /** diff --git a/source/java/org/alfresco/repo/lock/LockServiceImpl.java b/source/java/org/alfresco/repo/lock/LockServiceImpl.java index f69b1f151a..4c5f5ebc8b 100644 --- a/source/java/org/alfresco/repo/lock/LockServiceImpl.java +++ b/source/java/org/alfresco/repo/lock/LockServiceImpl.java @@ -66,7 +66,7 @@ import org.alfresco.service.namespace.QName; * @author Roy Wetherall */ public class LockServiceImpl implements LockService, - NodeServicePolicies.BeforeCreateChildAssociationPolicy, + NodeServicePolicies.OnCreateChildAssociationPolicy, NodeServicePolicies.BeforeUpdateNodePolicy, NodeServicePolicies.BeforeDeleteNodePolicy, CopyServicePolicies.OnCopyNodePolicy, @@ -179,31 +179,31 @@ public class LockServiceImpl implements LockService, { // Register the various class behaviours to enable lock checking this.policyComponent.bindAssociationBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "beforeCreateChildAssociation"), + NodeServicePolicies.OnCreateChildAssociationPolicy.QNAME, ContentModel.ASPECT_LOCKABLE, - new JavaBehaviour(this, "beforeCreateChildAssociation")); + new JavaBehaviour(this, "onCreateChildAssociation")); this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "beforeUpdateNode"), + NodeServicePolicies.BeforeUpdateNodePolicy.QNAME, ContentModel.ASPECT_LOCKABLE, new JavaBehaviour(this, "beforeUpdateNode")); this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), + NodeServicePolicies.BeforeDeleteNodePolicy.QNAME, ContentModel.ASPECT_LOCKABLE, new JavaBehaviour(this, "beforeDeleteNode")); // Register copy class behaviour this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + CopyServicePolicies.OnCopyNodePolicy.QNAME, ContentModel.ASPECT_LOCKABLE, new JavaBehaviour(this, "getCopyCallback")); // Register the onCreateVersion behavior for the version aspect this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "beforeCreateVersion"), + VersionServicePolicies.BeforeCreateVersionPolicy.QNAME, ContentModel.ASPECT_LOCKABLE, new JavaBehaviour(this, "beforeCreateVersion")); this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateVersion"), + VersionServicePolicies.OnCreateVersionPolicy.QNAME, ContentModel.ASPECT_LOCKABLE, new JavaBehaviour(this, "onCreateVersion")); } @@ -548,14 +548,9 @@ public class LockServiceImpl implements LockService, * * @see #checkForLock(NodeRef) */ - public void beforeCreateChildAssociation( - NodeRef parentNodeRef, - NodeRef childNodeRef, - QName assocTypeQName, - QName assocQName, - boolean isNewNode) + public void onCreateChildAssociation(ChildAssociationRef childAssocRef, boolean isNewNode) { - LockType lockType = getLockType(parentNodeRef); + LockType lockType = getLockType(childAssocRef.getParentRef()); if(lockType != null) { @@ -563,7 +558,7 @@ public class LockServiceImpl implements LockService, { case WRITE_LOCK: case READ_ONLY_LOCK: - checkForLock(parentNodeRef); + checkForLock(childAssocRef.getParentRef()); break; case NODE_LOCK: // don't check for lock diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index 197fb10ea7..dc01f65e45 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -26,7 +26,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.ResourceBundle.Control; import java.util.Set; import java.util.Stack; @@ -58,12 +60,15 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.search.QueryParameterDefinition; import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; +import org.alfresco.util.Pair; import org.alfresco.util.SearchLanguageConversion; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; /** * Implementation of the file/folder-specific service. @@ -103,9 +108,6 @@ public class FileFolderServiceImpl implements FileFolderService " and (subtypeOf('" + ContentModel.TYPE_CONTENT + "')" + " or subtypeOf('" + ContentModel.TYPE_LINK + "'))]"; - /** empty parameters */ - private static final QueryParameterDefinition[] PARAMS_ANY_NAME = new QueryParameterDefinition[1]; - private static Log logger = LogFactory.getLog(FileFolderServiceImpl.class); private NamespaceService namespaceService; @@ -366,6 +368,37 @@ public class FileFolderServiceImpl implements FileFolderService } + @Override + public NodeRef getLocalizedSibling(NodeRef nodeRef) + { + Locale userLocale = I18NUtil.getLocale(); + + String name = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); + // Work out the base name we are working with + Pair split = getExtension(name, false); + String base = split.getFirst(); + String ext = split.getSecond(); + + NodeRef resultNodeRef = nodeRef; + // Search for siblings with the same name + Control resourceHelper = Control.getControl(Control.FORMAT_DEFAULT); + List candidateLocales = resourceHelper.getCandidateLocales(base, userLocale); + for (Locale candidateLocale : candidateLocales) + { + String filename = resourceHelper.toBundleName(base, candidateLocale) + "." + ext; + // Attempt to find the file + NodeRef foundNodeRef = searchSimple(parentNodeRef, filename); + if (foundNodeRef != null) // TODO: Check for read permissions + { + resultNodeRef = foundNodeRef; + break; + } + } + // Done + return resultNodeRef; + } + public NodeRef searchSimple(NodeRef contextNodeRef, String name) { NodeRef childNodeRef = nodeService.getChildByName(contextNodeRef, ContentModel.ASSOC_CONTAINS, name); @@ -490,11 +523,6 @@ public class FileFolderServiceImpl implements FileFolderService true, namePattern); } - else - { - // name pattern is null so search for ANY_NAME - params = PARAMS_ANY_NAME; - } // determine the correct query to use String query = null; if (includeSubFolders) @@ -702,6 +730,7 @@ public class FileFolderServiceImpl implements FileFolderService /** * @see #moveOrCopy(NodeRef, NodeRef, String, boolean) */ + @Override public FileInfo move(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException { return moveOrCopy(sourceNodeRef, null, targetParentRef, newName, true); @@ -710,11 +739,21 @@ public class FileFolderServiceImpl implements FileFolderService /** * @see #moveOrCopy(NodeRef, NodeRef, String, boolean) */ - public FileInfo move(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException + @Override + public FileInfo moveFrom(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException { return moveOrCopy(sourceNodeRef, sourceParentRef, targetParentRef, newName, true); } + /** + * @deprecated + */ + @Override + public FileInfo move(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException + { + return moveOrCopy(sourceNodeRef, sourceParentRef, targetParentRef, newName, true); + } + /** * @see #moveOrCopy(NodeRef, NodeRef, String, boolean) */ @@ -908,8 +947,8 @@ public class FileFolderServiceImpl implements FileFolderService // 3. extension was not changed, // // It fixes the ETWOTWO-16 issue. - String oldExt = getExtension(beforeFileInfo.getName()); - String newExt = getExtension(newName); + String oldExt = getExtension(beforeFileInfo.getName(), true).getSecond(); + String newExt = getExtension(newName, true).getSecond(); if (contentData != null && newExt.length() != 0 && !"tmp".equalsIgnoreCase(newExt) && @@ -1192,19 +1231,24 @@ public class FileFolderServiceImpl implements FileFolderService // Done return writer; } - - private String getExtension(String name) + + /** + * Split a filename into the base (part before the '.') and the extension (part after the '.') + */ + private Pair getExtension(String name, boolean useLastDot) { - String result = ""; + String ext = ""; + String base = name; if (name != null) { name = name.trim(); - int index = name.lastIndexOf('.'); + int index = useLastDot ? name.lastIndexOf('.') : name.indexOf('.'); if (index > -1 && (index < name.length() - 1)) { - result = name.substring(index + 1); + base = name.substring(0, index); + ext = name.substring(index + 1); } } - return result; + return new Pair(base, ext); } } diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java index 91ee67851d..97dc5697c5 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java @@ -30,7 +30,6 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; import org.alfresco.error.AlfrescoRuntimeException; -import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.model.ContentModel; import org.alfresco.model.ForumModel; import org.alfresco.repo.content.MimetypeMap; @@ -39,6 +38,9 @@ import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.dictionary.M2Type; import org.alfresco.repo.node.integrity.IntegrityChecker; import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileFolderService; @@ -52,6 +54,8 @@ import org.alfresco.service.cmr.repository.CyclicChildRelationshipException; 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.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.view.ImporterService; import org.alfresco.service.cmr.view.Location; import org.alfresco.service.namespace.NamespaceService; @@ -59,7 +63,9 @@ import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.GUID; import org.springframework.context.ApplicationContext; +import org.springframework.extensions.surf.util.I18NUtil; /** * @see org.alfresco.repo.model.filefolder.FileFolderServiceImpl @@ -88,6 +94,9 @@ public class FileFolderServiceImplTest extends TestCase private TransactionService transactionService; private NodeService nodeService; private FileFolderService fileFolderService; + private PermissionService permissionService; + private MutableAuthenticationService authenticationService; + private AuthenticationComponent authenticationComponent; private DictionaryDAO dictionaryDAO; private UserTransaction txn; private NodeRef rootNodeRef; @@ -100,9 +109,10 @@ public class FileFolderServiceImplTest extends TestCase transactionService = serviceRegistry.getTransactionService(); nodeService = serviceRegistry.getNodeService(); fileFolderService = serviceRegistry.getFileFolderService(); + permissionService = serviceRegistry.getPermissionService(); + authenticationService = (MutableAuthenticationService) ctx.getBean("AuthenticationService"); dictionaryDAO = (DictionaryDAO) ctx.getBean("dictionaryDAO"); - AuthenticationComponent authenticationComponent = (AuthenticationComponent) ctx - .getBean("authenticationComponent"); + authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); // start the transaction txn = transactionService.getUserTransaction(); @@ -120,9 +130,11 @@ public class FileFolderServiceImplTest extends TestCase rootNodeRef = nodeService.getRootNode(storeRef); // create a folder to import into - workingRootNodeRef = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, - QName.createQName(NamespaceService.ALFRESCO_URI, "working root"), ContentModel.TYPE_FOLDER) - .getChildRef(); + workingRootNodeRef = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.ALFRESCO_URI, "working root"), + ContentModel.TYPE_FOLDER).getChildRef(); // import the test data ImporterService importerService = serviceRegistry.getImporterService(); @@ -454,6 +466,112 @@ public class FileFolderServiceImplTest extends TestCase // Move to a target folder but with a rename to avoid the name clash fileFolderService.move(fileA.getNodeRef(), folderB.getNodeRef(), NAME_L1_FILE_B); } + + /** + * ALF-7692 + */ + @SuppressWarnings("deprecation") + public void testMovePermissions() throws Exception + { + txn.commit(); + + // Create a target folder to write to. Folder owner is 'system'. + RunAsWork createTargetWork = new RunAsWork() + { + @Override + public NodeRef doWork() throws Exception + { + // Create folder TARGET + return fileFolderService.create( + workingRootNodeRef, + "TARGET", + ContentModel.TYPE_FOLDER).getNodeRef(); + } + }; + final NodeRef targetNodeRef = AuthenticationUtil.runAs(createTargetWork, AuthenticationUtil.getSystemUserName()); + + // Use a specific user + String username = "Mover-" + GUID.generate(); + char[] password = "mover".toCharArray(); + authenticationService.createAuthentication(username, password); + permissionService.setPermission( + rootNodeRef, + username, + PermissionService.ALL_PERMISSIONS, + true); + AuthenticationUtil.clearCurrentSecurityContext(); + authenticationComponent.authenticate(username, password); + + // Check that we can write to the target while permissions allow it + fileFolderService.create( + targetNodeRef, + "SOURCE ONE", + ContentModel.TYPE_CONTENT).getNodeRef(); + // Deny anyone access to the target + RunAsWork setPermissionsWork = new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + permissionService.setInheritParentPermissions(targetNodeRef, false); + permissionService.setPermission( + targetNodeRef, + PermissionService.ALL_AUTHORITIES, + PermissionService.ALL_PERMISSIONS, + false); + return null; + } + }; + AuthenticationUtil.runAs(setPermissionsWork, AuthenticationUtil.getSystemUserName()); + try + { + fileFolderService.create( + targetNodeRef, + "SOURCE TWO", + ContentModel.TYPE_CONTENT).getNodeRef(); + fail("Expected permissions to deny a write"); + } + catch (AccessDeniedException e) + { + // Expected + } + + // Create source to move + NodeRef movingNodeRef = fileFolderService.create( + workingRootNodeRef, + "SOURCE THREE", + ContentModel.TYPE_CONTENT).getNodeRef(); + // Move it + try + { + fileFolderService.moveFrom(movingNodeRef, workingRootNodeRef, targetNodeRef, "SOURCE THREE"); + fail("Expected permissions to deny the move"); + } + catch (AccessDeniedException e) + { + // Expected + } + // Move it + try + { + fileFolderService.move(movingNodeRef, targetNodeRef, "SOURCE FOUR"); + fail("Expected permissions to deny the move"); + } + catch (AccessDeniedException e) + { + // Expected + } + // Copy it + try + { + fileFolderService.copy(movingNodeRef, targetNodeRef, "SOURCE FIVE"); + fail("Expected permissions to deny the copy"); + } + catch (AccessDeniedException e) + { + // Expected + } + } public void testCopy() throws Exception { @@ -777,4 +895,47 @@ public class FileFolderServiceImplTest extends TestCase ContentReader reader = fileFolderService.getReader(fileNodeRef); assertEquals("Mimetype was not automatically set", MimetypeMap.MIMETYPE_HTML, reader.getMimetype()); } + + @SuppressWarnings("unused") + public void testGetLocalizedSibling() throws Exception + { + FileInfo base = fileFolderService.create(workingRootNodeRef, "Something.ftl", ContentModel.TYPE_CONTENT); + NodeRef node = base.getNodeRef(); + NodeRef nodeFr = fileFolderService.create(workingRootNodeRef, "Something_fr.ftl", ContentModel.TYPE_CONTENT).getNodeRef(); + NodeRef nodeFrFr = fileFolderService.create(workingRootNodeRef, "Something_fr_FR..ftl", ContentModel.TYPE_CONTENT).getNodeRef(); + NodeRef nodeEn = fileFolderService.create(workingRootNodeRef, "Something_en.ftl", ContentModel.TYPE_CONTENT).getNodeRef(); + NodeRef nodeEnUs = fileFolderService.create(workingRootNodeRef, "Something_en_US.ftl", ContentModel.TYPE_CONTENT).getNodeRef(); + + I18NUtil.setLocale(Locale.US); + assertEquals("Match fail for " + I18NUtil.getLocale(), nodeEnUs, fileFolderService.getLocalizedSibling(node)); + I18NUtil.setLocale(Locale.UK); + assertEquals("Match fail for " + I18NUtil.getLocale(), nodeEn, fileFolderService.getLocalizedSibling(node)); + I18NUtil.setLocale(Locale.CHINESE); + assertEquals("Match fail for " + I18NUtil.getLocale(), node, fileFolderService.getLocalizedSibling(node)); + + // Now use French as the base and check that the original is returned + + I18NUtil.setLocale(Locale.US); + assertEquals("Match fail for " + I18NUtil.getLocale(), nodeFr, fileFolderService.getLocalizedSibling(nodeFr)); + I18NUtil.setLocale(Locale.UK); + assertEquals("Match fail for " + I18NUtil.getLocale(), nodeFr, fileFolderService.getLocalizedSibling(nodeFr)); + I18NUtil.setLocale(Locale.CHINESE); + assertEquals("Match fail for " + I18NUtil.getLocale(), nodeFr, fileFolderService.getLocalizedSibling(nodeFr)); + + + // Check that extensions like .get.html.ftl work + FileInfo mbase = fileFolderService.create(workingRootNodeRef, "Another.get.html.ftl", ContentModel.TYPE_CONTENT); + NodeRef mnode = mbase.getNodeRef(); + NodeRef mnodeFr = fileFolderService.create(workingRootNodeRef, "Another_fr.get.html.ftl", ContentModel.TYPE_CONTENT).getNodeRef(); + + // Should get the base version, except for when French + I18NUtil.setLocale(Locale.UK); + assertEquals("Match fail for " + I18NUtil.getLocale(), mnode, fileFolderService.getLocalizedSibling(mnode)); + I18NUtil.setLocale(Locale.FRENCH); + assertEquals("Match fail for " + I18NUtil.getLocale(), mnodeFr, fileFolderService.getLocalizedSibling(mnode)); + I18NUtil.setLocale(Locale.CHINESE); + assertEquals("Match fail for " + I18NUtil.getLocale(), mnode, fileFolderService.getLocalizedSibling(mnode)); + I18NUtil.setLocale(Locale.US); + assertEquals("Match fail for " + I18NUtil.getLocale(), mnode, fileFolderService.getLocalizedSibling(mnode)); + } } diff --git a/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java b/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java index 188103c181..a18833387e 100644 --- a/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java +++ b/source/java/org/alfresco/repo/model/filefolder/MLTranslationInterceptor.java @@ -71,6 +71,7 @@ public class MLTranslationInterceptor implements MethodInterceptor METHOD_NAMES_LIST.add("getNamePath"); METHOD_NAMES_SINGLE = new HashSet(13); + METHOD_NAMES_SINGLE.add("getLocalizedSibling"); METHOD_NAMES_SINGLE.add("searchSimple"); METHOD_NAMES_SINGLE.add("rename"); METHOD_NAMES_SINGLE.add("move"); diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java index 1af5ed4209..7136a99c8a 100644 --- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java @@ -28,7 +28,6 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeServicePolicies.BeforeAddAspectPolicy; -import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateChildAssociationPolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateNodeAssociationPolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateStorePolicy; @@ -121,7 +120,6 @@ public abstract class AbstractNodeServiceImpl implements NodeService private ClassPolicyDelegate onRemoveAspectDelegate; private AssociationPolicyDelegate beforeCreateNodeAssociationDelegate; private AssociationPolicyDelegate onCreateNodeAssociationDelegate; - private AssociationPolicyDelegate beforeCreateChildAssociationDelegate; private AssociationPolicyDelegate onCreateChildAssociationDelegate; private AssociationPolicyDelegate beforeDeleteChildAssociationDelegate; private AssociationPolicyDelegate onDeleteChildAssociationDelegate; @@ -212,7 +210,6 @@ public abstract class AbstractNodeServiceImpl implements NodeService beforeCreateNodeAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.BeforeCreateNodeAssociationPolicy.class); onCreateNodeAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnCreateNodeAssociationPolicy.class); - beforeCreateChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.BeforeCreateChildAssociationPolicy.class); onCreateChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnCreateChildAssociationPolicy.class); beforeDeleteChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.BeforeDeleteChildAssociationPolicy.class); onDeleteChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnDeleteChildAssociationPolicy.class); @@ -569,26 +566,6 @@ public abstract class AbstractNodeServiceImpl implements NodeService policy.onCreateNodeAssociation(childAssocRef); } - /** - * @see NodeServicePolicies.BeforeCreateChildAssociationPolicy#beforeCreateChildAssociation(NodeRef, - * NodeRef, QName, QName) - * - * @deprecated V3.3: We don't know the child node reference until the association has been created - */ - protected void invokeBeforeCreateChildAssociation(NodeRef parentNodeRef, NodeRef childNodeRef, QName assocTypeQName, QName assocQName, boolean isNewNode) - { - if (ignorePolicy(parentNodeRef)) - { - return; - } - - // get qnames to invoke against - Set qnames = getTypeAndAspectQNames(parentNodeRef); - // execute policy for node type - NodeServicePolicies.BeforeCreateChildAssociationPolicy policy = beforeCreateChildAssociationDelegate.get(parentNodeRef, qnames, assocTypeQName); - policy.beforeCreateChildAssociation(parentNodeRef, childNodeRef, assocTypeQName, assocQName, isNewNode); - } - /** * @see NodeServicePolicies.OnCreateChildAssociationPolicy#onCreateChildAssociation(ChildAssociationRef) */ diff --git a/source/java/org/alfresco/repo/node/NodeServicePolicies.java b/source/java/org/alfresco/repo/node/NodeServicePolicies.java index ea8e6ebc2c..81c714f8e3 100644 --- a/source/java/org/alfresco/repo/node/NodeServicePolicies.java +++ b/source/java/org/alfresco/repo/node/NodeServicePolicies.java @@ -256,29 +256,6 @@ public interface NodeServicePolicies public void onCreateNodeAssociation(ChildAssociationRef childAssocRef); } - public interface BeforeCreateChildAssociationPolicy extends AssociationPolicy - { - public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "beforeCreateChildAssociation"); - /** - * Called before a node child association is created. - * - * @param parentNodeRef the parent node reference - * @param childNodeRef the child node reference - * @param assocTypeQName the type of the association - * @param assocQName the name of the association - * @param isNewNode true if the node is new or false if the node is being linked in - * - * @deprecated This is not necessary and we don't know about the child node until after the - * association has been created. - */ - public void beforeCreateChildAssociation( - NodeRef parentNodeRef, - NodeRef childNodeRef, - QName assocTypeQName, - QName assocQName, - boolean isNewNode); - } - public interface OnCreateChildAssociationPolicy extends AssociationPolicy { public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateChildAssociation"); diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 452b67668e..a023c53838 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -257,7 +257,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl /** * {@inheritDoc} */ - @SuppressWarnings("deprecation") public ChildAssociationRef createNode( NodeRef parentRef, QName assocTypeQName, @@ -334,10 +333,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl Map propertiesAfter = nodeDAO.getNodeProperties(childNodePair.getFirst()); - // We now have enough to declare the child association creation - // TODO: Remove in call - invokeBeforeCreateChildAssociation(parentRef, childNodePair.getSecond(), assocTypeQName, assocQName, true); - // Invoke policy behaviour invokeOnCreateNode(childAssocRef); invokeOnCreateChildAssociation(childAssocRef, true); @@ -1108,9 +1103,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl Long parentNodeId = parentNodePair.getFirst(); parentNodePairs.add(parentNodePair); - // Invoke policy behaviours - invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName, false); - // make the association Pair childAssocPair = nodeDAO.newChildAssoc( parentNodeId, childNodeId, @@ -2165,7 +2157,6 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl else { invokeBeforeDeleteChildAssociation(oldParentAssocRef); - invokeBeforeCreateChildAssociation(newParentRef, nodeToMoveRef, assocTypeQName, assocQName, false); } // Move node under the new parent diff --git a/source/java/org/alfresco/repo/node/index/AVMFullIndexRecoveryComponent.java b/source/java/org/alfresco/repo/node/index/AVMFullIndexRecoveryComponent.java index 1c86f056f5..4364c1eaa6 100644 --- a/source/java/org/alfresco/repo/node/index/AVMFullIndexRecoveryComponent.java +++ b/source/java/org/alfresco/repo/node/index/AVMFullIndexRecoveryComponent.java @@ -27,6 +27,8 @@ import org.alfresco.repo.search.IndexMode; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMStoreDescriptor; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -254,14 +256,15 @@ public class AVMFullIndexRecoveryComponent extends AbstractReindexComponent private void processStore(String store, RecoveryMode mode) { + QName vetoName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "AVMFullIndexRecoveryComponent"); + // put the server into read-only mode for the duration - boolean allowWrite = !transactionService.isReadOnly(); try { if (lockServer) { // set the server into read-only mode - transactionService.setAllowWrite(false); + transactionService.setAllowWrite(false, vetoName); } recoverStore(store, mode); @@ -269,8 +272,8 @@ public class AVMFullIndexRecoveryComponent extends AbstractReindexComponent } finally { - // restore read-only state - transactionService.setAllowWrite(allowWrite); + // remove veto + transactionService.setAllowWrite(true, vetoName); } } diff --git a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java index 486a4a1ae1..44e05033fa 100644 --- a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java +++ b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java @@ -632,9 +632,9 @@ public abstract class AbstractReindexComponent implements IndexRecovery void reindexedNode(NodeRef nodeRef); } - protected void reindexTransaction(Long txnId) + protected void reindexTransaction(Long txnId, boolean isFull) { - reindexTransaction(txnId, null); + reindexTransaction(txnId, null, isFull); } /** @@ -646,7 +646,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery * * @throws ReindexTerminatedException if the VM is shutdown during the reindex */ - protected void reindexTransaction(final long txnId, ReindexNodeCallback callback) + protected void reindexTransaction(final long txnId, ReindexNodeCallback callback, boolean isFull) { ParameterCheck.mandatory("txnId", txnId); if (logger.isDebugEnabled()) @@ -672,18 +672,33 @@ public abstract class AbstractReindexComponent implements IndexRecovery } if (nodeStatus.isDeleted()) // node deleted { - // only the child node ref is relevant - ChildAssociationRef assocRef = new ChildAssociationRef( - ContentModel.ASSOC_CHILDREN, - null, - null, - nodeRef); - indexer.deleteNode(assocRef); + if(isFull == false) + { + // only the child node ref is relevant + ChildAssociationRef assocRef = new ChildAssociationRef( + ContentModel.ASSOC_CHILDREN, + null, + null, + nodeRef); + indexer.deleteNode(assocRef); + } } else // node created { - // reindex - indexer.updateNode(nodeRef); + if(isFull) + { + ChildAssociationRef assocRef = new ChildAssociationRef( + ContentModel.ASSOC_CHILDREN, + null, + null, + nodeRef); + indexer.createNode(assocRef); + } + else + { + // reindex + indexer.updateNode(nodeRef); + } } // Make the callback if (callback != null) @@ -718,8 +733,9 @@ public abstract class AbstractReindexComponent implements IndexRecovery private final List txnIds; private long lastIndexedTimestamp; private boolean atHeadOfQueue; + private boolean isFull; - private ReindexWorkerRunnable(List txnIds) + private ReindexWorkerRunnable(List txnIds, boolean isFull) { this.id = ID_GENERATOR.addAndGet(1); if (ID_GENERATOR.get() > 1000) @@ -728,6 +744,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery } this.uidHashCode = id * 13 + 11; this.txnIds = txnIds; + this.isFull = isFull; this.atHeadOfQueue = false; recordTimestamp(); } @@ -807,7 +824,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery id, txnId.longValue()); loggerOnThread.debug(msg); } - reindexTransaction(txnId, ReindexWorkerRunnable.this); + reindexTransaction(txnId, ReindexWorkerRunnable.this, isFull); } // Done return null; @@ -1000,7 +1017,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery * @see #waitForAsynchronousReindexing() * @since 2.1.4 */ - protected void reindexTransactionAsynchronously(final List txnIds) + protected void reindexTransactionAsynchronously(final List txnIds, final boolean isFull) { // Bypass if there is no thread pool if (threadPoolExecutor == null || threadPoolExecutor.getMaximumPoolSize() < 2) @@ -1025,7 +1042,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery txnId.longValue()); loggerOnThread.debug(msg); } - reindexTransaction(txnId, null); + reindexTransaction(txnId, null, isFull); } return null; } @@ -1034,7 +1051,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery return; } - ReindexWorkerRunnable runnable = new ReindexWorkerRunnable(txnIds); + ReindexWorkerRunnable runnable = new ReindexWorkerRunnable(txnIds, isFull); try { reindexThreadLock.writeLock().lock(); diff --git a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java index 7468068016..3266284287 100644 --- a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java +++ b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java @@ -31,6 +31,8 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.I18NUtil; @@ -65,11 +67,9 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent /** Do nothing - not even a check. */ NONE, /** - * Perform a quick check on the state of the indexes only. This only checks that the - * first N transactions are present in the index and doesn't guarantee that the indexes - * are wholely consistent. Normally, the indexes are consistent up to a certain time. - * The system does a precautionary index top-up by default, so the last transactions are - * not validated. + * Perform a quick check on the state of the indexes. This only checks that the + * first N and last M transactions are present in the index and doesn't guarantee that + * the indexes are wholely consistent. Normally, the indexes are consistent up to a certain time. */ VALIDATE, /** @@ -91,6 +91,9 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent private boolean stopOnError; private int maxTransactionsPerLuceneCommit; + private final QName vetoName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "FullIndexRecoveryComponent"); + + /** *
    *
  • recoveryMode: VALIDATE
  • @@ -178,8 +181,9 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent { if (lockServer) { + // set the server into read-only mode - transactionService.setAllowWrite(false); + transactionService.setAllowWrite(false, vetoName); } @@ -223,7 +227,7 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent finally { // restore read-only state - transactionService.setAllowWrite(allowWrite); + transactionService.setAllowWrite(true, vetoName); } } @@ -385,7 +389,7 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent { try { - reindexTransactionAsynchronously(txnIdBuffer); + reindexTransactionAsynchronously(txnIdBuffer, true); } catch (Throwable e) { diff --git a/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java b/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java index 315d5557b6..39cea77b48 100644 --- a/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java +++ b/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java @@ -655,7 +655,7 @@ found: try { // We try the reindex, but for the sake of continuity, have to let it run on - reindexTransactionAsynchronously(txnIdBuffer); + reindexTransactionAsynchronously(txnIdBuffer, false); } catch (Throwable e) { diff --git a/source/java/org/alfresco/repo/ownable/impl/OwnableServiceTest.java b/source/java/org/alfresco/repo/ownable/impl/OwnableServiceTest.java index 5872f4dee7..ab7be5b4b0 100644 --- a/source/java/org/alfresco/repo/ownable/impl/OwnableServiceTest.java +++ b/source/java/org/alfresco/repo/ownable/impl/OwnableServiceTest.java @@ -25,10 +25,13 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; import org.alfresco.repo.security.permissions.dynamic.OwnerDynamicAuthority; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -76,6 +79,13 @@ public class OwnableServiceTest extends TestCase public void setUp() throws Exception { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + nodeService = (NodeService) ctx.getBean("nodeService"); authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService"); authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); diff --git a/source/java/org/alfresco/repo/policy/PolicyComponent.java b/source/java/org/alfresco/repo/policy/PolicyComponent.java index d1ebfbccc1..8a27bd2b2f 100644 --- a/source/java/org/alfresco/repo/policy/PolicyComponent.java +++ b/source/java/org/alfresco/repo/policy/PolicyComponent.java @@ -172,10 +172,10 @@ public interface PolicyComponent * For example, before a rule folder association is created. *
          *         policyComponent.bindAssociationBehaviour(
    -     *           NodeServicePolicies.BeforeCreateChildAssociationPolicy.QNAME,
    +     *           NodeServicePolicies.OnCreateChildAssociationPolicy.QNAME,
          *           RuleModel.ASPECT_RULES,
          *           RuleModel.ASSOC_RULE_FOLDER,
    -     *           new JavaBehaviour(this, "beforeCreateChildAssociation"));
    +     *           new JavaBehaviour(this, "OnCreateChildAssociation"));
          * 
    * * @param policy the policy name diff --git a/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java b/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java index 0b80ce0cad..12d194f6eb 100644 --- a/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java +++ b/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java @@ -186,10 +186,10 @@ public class PreferenceServiceImpl implements PreferenceService boolean result = true; // Split strings - name = name.replace(".", "-"); - String[] nameArr = name.split("-"); - matchTo = matchTo.replace(".", "-"); - String[] matchToArr = matchTo.split("-"); + name = name.replace(".", "+"); + String[] nameArr = name.split("\\+"); + matchTo = matchTo.replace(".", "+"); + String[] matchToArr = matchTo.split("\\+"); int index = 0; for (String matchToElement : matchToArr) diff --git a/source/java/org/alfresco/repo/preference/script/ScriptPreferenceService.java b/source/java/org/alfresco/repo/preference/script/ScriptPreferenceService.java index e3cd790dbb..48f32858e4 100644 --- a/source/java/org/alfresco/repo/preference/script/ScriptPreferenceService.java +++ b/source/java/org/alfresco/repo/preference/script/ScriptPreferenceService.java @@ -60,7 +60,7 @@ public class ScriptPreferenceService extends BaseScopableProcessorExtension for (Map.Entry entry : prefs.entrySet()) { - String[] keys = entry.getKey().replace(".", "-").split("-"); + String[] keys = entry.getKey().replace(".", "+").split("\\+"); setPrefValue(keys, entry.getValue(), result); } diff --git a/source/java/org/alfresco/repo/rating/AbstractRatingRollupAlgorithm.java b/source/java/org/alfresco/repo/rating/AbstractRatingRollupAlgorithm.java new file mode 100644 index 0000000000..824e4c2ac9 --- /dev/null +++ b/source/java/org/alfresco/repo/rating/AbstractRatingRollupAlgorithm.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2005-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 . + */ + +package org.alfresco.repo.rating; + +import java.io.Serializable; + +import org.alfresco.service.cmr.rating.RatingService; +import org.alfresco.service.cmr.rating.RatingServiceException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.NamespaceService; +import org.springframework.beans.factory.InitializingBean; + +/** + * This class provides the basic implementation of a rating property rollup. + * By providing an implementation of this class (or reusing an existing one), + * injecting the object into the {@link RatingScheme} and following the content + * model naming conventions described in {@link RatingRollupNamingConventionsUtil}, it + * should be possible to have new rating property rollups automatically calculated + * and persisted into the Alfresco content model, thereby enabling indexing, searching + * and sorting of rating-related properties. + * + * @author Neil McErlean + * @since 3.5 + */ +public abstract class AbstractRatingRollupAlgorithm implements InitializingBean +{ + protected String ratingSchemeName; + protected NamespaceService namespaceService; + protected NodeService nodeService; + protected RatingServiceImpl ratingServiceImpl; + + protected final String rollupName; + + public AbstractRatingRollupAlgorithm(String rollupName) + { + this.rollupName = rollupName; + } + + public void setRatingSchemeName(String ratingScheme) + { + this.ratingSchemeName = ratingScheme; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setRatingService(RatingService ratingService) + { + this.ratingServiceImpl = (RatingServiceImpl)ratingService; + } + + @Override + public void afterPropertiesSet() throws Exception + { + if (ratingSchemeName == null) + { + throw new RatingServiceException("Illegal null ratingSchemeName in " + this.getClass().getSimpleName()); + } + } + + public abstract Serializable recalculate(NodeRef ratedNode); + + /** + * This method returns the rollup name, for example "Total" or "Count". + * This rollup name is used as part of the convention-based naming of content model + * property names see {@link RatingRollupNamingConventionsUtil}. + * @return + */ + public String getRollupName() + { + return this.rollupName; + } +} diff --git a/source/java/org/alfresco/repo/rating/RatingCountRollupAlgorithm.java b/source/java/org/alfresco/repo/rating/RatingCountRollupAlgorithm.java new file mode 100644 index 0000000000..eaf1334506 --- /dev/null +++ b/source/java/org/alfresco/repo/rating/RatingCountRollupAlgorithm.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2005-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 . + */ + +package org.alfresco.repo.rating; + +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.rating.Rating; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * An implementation of {@link AbstractRatingRollupAlgorithm} which calculates the count (number) of all + * ratings in a given scheme. + * + * @author Neil McErlean + * @since 3.5 + */ +public class RatingCountRollupAlgorithm extends AbstractRatingRollupAlgorithm +{ + public static final String ROLLUP_NAME = "Count"; + + public RatingCountRollupAlgorithm() + { + super(ROLLUP_NAME); + } + + public Integer recalculate(NodeRef ratedNode) + { + int result = 0; + + // If the node is not rateable, then it has no ratings in any scheme. + if (nodeService.hasAspect(ratedNode, ContentModel.ASPECT_RATEABLE)) + { + List ratingsNodes = ratingServiceImpl.getRatingNodeChildren(ratedNode, ratingSchemeName, null); + // Filter by scheme + for (ChildAssociationRef chAssRef : ratingsNodes) + { + NodeRef nextRatingNode = chAssRef.getChildRef(); + Rating rating = ratingServiceImpl.getRatingFrom(nextRatingNode); + if (ratingSchemeName.equals(rating.getScheme().getName())) + { + result++; + } + } + } + + return result; + } +} diff --git a/source/java/org/alfresco/repo/rating/RatingNamingConventionsUtil.java b/source/java/org/alfresco/repo/rating/RatingNamingConventionsUtil.java new file mode 100644 index 0000000000..1468f2dc28 --- /dev/null +++ b/source/java/org/alfresco/repo/rating/RatingNamingConventionsUtil.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2005-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 . + */ + +package org.alfresco.repo.rating; + +import org.alfresco.service.cmr.rating.RatingScheme; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.QNamePattern; +import org.alfresco.service.namespace.RegexQNamePattern; + +/** + * This class encapsulates the naming conventions used in the RatingService. + * e.g. the naming conventions used for the aspect and for the properties in the content model. + *

    + * Each rating scheme which has one or more rating property rollups, will lead to the + * addition of an aspect on to the rated node. This aspect is named: + *

    + * "cm:" + <ratingSchemeName> + "Rollups"
    + * e.g. cm:likesRatingSchemeRollups
    + * 
    + * Then within that aspect, any rolled up property values will be persisted in a property named: + *
    + * "cm:" + <ratingSchemeName> + <rollupName>
    + * e.g. cm:likesRatingSchemeCount
    + * 
    + * The ratingSchemeName is the spring bean name of the rating scheme and the rollupName is + * the rollup name as defined in the algorithm class e.g. {@link RatingCountRollupAlgorithm#ROLLUP_NAME}. + * + * @author Neil McErlean + * @since 3.5 + */ +public class RatingNamingConventionsUtil +{ + private static final String RATING_ASSOC_SEPARATOR = "__"; + private NamespaceService namespaceService; + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * This method returns the {@link QName association name} that will be used to link + * a cm:rateable node to its cm:rating child for the specified username and ratingSchemeName. + */ + public QName getRatingAssocNameFor(String username, String ratingSchemeName) + { + StringBuilder compoundString = new StringBuilder(); + compoundString.append("cm:").append(username).append(RATING_ASSOC_SEPARATOR).append(ratingSchemeName); + QName result = QName.createQName(compoundString.toString(), namespaceService); + + return result; + } + + /** + * This method returns a QNamePattern for the specified username and ratingSchemeName. + * This pattern can be used when navigating child-associations looking for cm:rating nodes. + * + * @param username the username to match against or null for all usernames. + * @param ratingSchemeName the ratingSchemeName to match against or null for all ratingSchemes. + * @return + */ + public QNamePattern getRatingAssocPatternForUser(String username, String ratingSchemeName) + { + if (username == null) + { + username = ".*"; + } + if (ratingSchemeName == null) + { + ratingSchemeName = ".*"; + } + return new RegexQNamePattern(NamespaceService.CONTENT_MODEL_1_0_URI, username + RATING_ASSOC_SEPARATOR + ratingSchemeName); + } + + /** + * Given a ratingSchemeName, this method returns the aspect name which would + * by convention be used to store rating property rollups. + * + * @param ratingSchemeName the ratingSchemeName, which is the spring bean name. + * @return the aspect name used to store all property rollups for that scheme. + */ + public QName getRollupAspectNameFor(String ratingSchemeName) + { + String result = "cm:" + ratingSchemeName + "Rollups"; + return QName.createQName(result, namespaceService); + } + + /** + * Given a ratingScheme, this method returns the aspect name which would + * by convention be used to store rating property rollups. + * + * @param ratingScheme the ratingScheme. + * @return the aspect name used to store all property rollups for that scheme. + */ + public QName getRollupAspectNameFor(RatingScheme ratingScheme) + { + return getRollupAspectNameFor(ratingScheme.getName()); + } + + /** + * Given a ratingSchemeName and a rollup name, this method returns the property name + * which would by convention be used to store the given rollup. + * + * @param ratingSchemeName the ratingSchemeName, which is the spring bean name. + * @param rollupName the name of the property rollup as given by {@link AbstractRatingRollupAlgorithm#getRollupName()}. + * @return the property name used to persist the given rollup in the given scheme. + */ + public QName getRollupPropertyNameFor(String ratingSchemeName, String rollupName) + { + String result = "cm:" + ratingSchemeName + rollupName; + return QName.createQName(result, namespaceService); + } + + /** + * Given a ratingScheme and a rollup name, this method returns the property name + * which would by convention be used to store the given rollup. + * + * @param ratingScheme the ratingScheme. + * @param rollupName the name of the property rollup as given by {@link AbstractRatingRollupAlgorithm#getRollupName()}. + * @return the property name used to persist the given rollup in the given scheme. + */ + public QName getRollupPropertyNameFor(RatingScheme ratingScheme, String rollupName) + { + return getRollupPropertyNameFor(ratingScheme.getName(), rollupName); + } +} diff --git a/source/java/org/alfresco/repo/rating/RatingSchemeImpl.java b/source/java/org/alfresco/repo/rating/RatingSchemeImpl.java index 8af8ed8366..09ad4daaad 100644 --- a/source/java/org/alfresco/repo/rating/RatingSchemeImpl.java +++ b/source/java/org/alfresco/repo/rating/RatingSchemeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -19,6 +19,10 @@ package org.alfresco.repo.rating; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + import org.alfresco.service.cmr.rating.RatingScheme; import org.alfresco.service.cmr.rating.RatingServiceException; import org.springframework.beans.factory.BeanNameAware; @@ -35,15 +39,33 @@ public class RatingSchemeImpl implements RatingScheme, BeanNameAware, Initializi private String name; private float minRating, maxRating; + private List propertyRollups = Collections.emptyList(); + + /** + * Is the cm:creator of the content node allowed to apply ratings to it? + * true if yes, else false. + */ + private boolean selfRatingAllowed; + public RatingSchemeImpl(RatingSchemeRegistry registry) { this.ratingSchemeRegistry = registry; } + public void setPropertyRollups(List rollupAlgorithms) + { + this.propertyRollups = rollupAlgorithms; + } + public void init() { ratingSchemeRegistry.register(this.name, this); } + + public List getPropertyRollups() + { + return Collections.unmodifiableList(this.propertyRollups); + } /* * (non-Javadoc) @@ -63,6 +85,11 @@ public class RatingSchemeImpl implements RatingScheme, BeanNameAware, Initializi { this.maxRating = maxRating; } + + public void setSelfRatingAllowed(boolean selfRatingAllowed) + { + this.selfRatingAllowed = selfRatingAllowed; + } /* * (non-Javadoc) @@ -98,6 +125,15 @@ public class RatingSchemeImpl implements RatingScheme, BeanNameAware, Initializi return this.minRating; } + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rating.RatingScheme#isSelfRatingAllowed() + */ + public boolean isSelfRatingAllowed() + { + return this.selfRatingAllowed; + } + /* * (non-Javadoc) * @see org.alfresco.service.cmr.rating.RatingScheme#getName() @@ -116,6 +152,20 @@ public class RatingSchemeImpl implements RatingScheme, BeanNameAware, Initializi .append(" [").append(this.minRating) .append("..").append(this.maxRating) .append("]"); + + // Injected rollups. + msg.append(" <"); + for (Iterator iter = propertyRollups.iterator(); iter.hasNext(); ) + { + AbstractRatingRollupAlgorithm nextRollup = iter.next(); + msg.append(nextRollup.getRollupName()); + if (iter.hasNext()) + { + msg.append(", "); + } + } + msg.append(">"); + return msg.toString(); } } diff --git a/source/java/org/alfresco/repo/rating/RatingServiceImpl.java b/source/java/org/alfresco/repo/rating/RatingServiceImpl.java index 12a569771e..4c846f6d4a 100644 --- a/source/java/org/alfresco/repo/rating/RatingServiceImpl.java +++ b/source/java/org/alfresco/repo/rating/RatingServiceImpl.java @@ -20,6 +20,7 @@ package org.alfresco.repo.rating; import java.io.Serializable; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -28,6 +29,8 @@ import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.rating.Rating; import org.alfresco.service.cmr.rating.RatingScheme; import org.alfresco.service.cmr.rating.RatingService; @@ -35,10 +38,8 @@ import org.alfresco.service.cmr.rating.RatingServiceException; 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.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNamePattern; -import org.alfresco.service.namespace.RegexQNamePattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -52,14 +53,22 @@ public class RatingServiceImpl implements RatingService private RatingSchemeRegistry schemeRegistry; // Injected services + private DictionaryService dictionaryService; private NodeService nodeService; private BehaviourFilter behaviourFilter; + private RatingNamingConventionsUtil ratingNamingConventions; + public void setRatingSchemeRegistry(RatingSchemeRegistry schemeRegistry) { this.schemeRegistry = schemeRegistry; } + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; @@ -70,6 +79,11 @@ public class RatingServiceImpl implements RatingService this.behaviourFilter = behaviourFilter; } + public void setRollupNamingConventions(RatingNamingConventionsUtil namingConventions) + { + this.ratingNamingConventions = namingConventions; + } + public Map getRatingSchemes() { // This is already an unmodifiable Map. @@ -90,9 +104,9 @@ public class RatingServiceImpl implements RatingService { final String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); boolean isCreator = isCurrentUserNodeCreator(targetNode); - if (isCreator) + if (isCreator && this.getRatingScheme(ratingSchemeName).isSelfRatingAllowed() == false) { - throw new RatingServiceException("Users can't rate their own content."); + throw new RatingServiceException("Users can't rate their own content for scheme " + ratingSchemeName); } AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { @@ -104,10 +118,12 @@ public class RatingServiceImpl implements RatingService }, AuthenticationUtil.getSystemUserName()); } + /** + * This method checks if the current fully authenticated user is the cm:creator of the specified node. + */ private boolean isCurrentUserNodeCreator(NodeRef targetNode) { final String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - // TODO Is creator the right property to use here? Serializable creator = nodeService.getProperty(targetNode, ContentModel.PROP_CREATOR); return currentUser.equals(creator); } @@ -135,6 +151,12 @@ public class RatingServiceImpl implements RatingService { throw new RatingServiceException("Rating " + rating + " violates range for " + ratingScheme); } + + // To support rolling up rating totals, counts etc, we use aspects named by convention. + // Before we start writing data into the db, we'll check that an aspect has been defined + // with the expected name. + QName rollupAspectName = ratingNamingConventions.getRollupAspectNameFor(ratingScheme); + final boolean rollupAspectIsDefined = dictionaryService.getAspect(rollupAspectName) != null; // Ensure that the application of a rating does not cause updates @@ -146,6 +168,13 @@ public class RatingServiceImpl implements RatingService { // Add the cm:rateable aspect if it's not there already. nodeService.addAspect(targetNode, ContentModel.ASPECT_RATEABLE, null); + + // We'll also add the rollup aspect specific for this rating scheme - if one has been defined in the content model. + if (rollupAspectIsDefined) + { + nodeService.addAspect(targetNode, rollupAspectName, null); + } + } finally { @@ -153,13 +182,14 @@ public class RatingServiceImpl implements RatingService } } - // We're looking for child cm:rating nodes whose qname matches the current user. - // i.e. we're looking for previously applied ratings by this user. - final QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, userName); + // We're looking for child cm:rating nodes whose assoc qname matches the current user & rating scheme. + // i.e. we're looking for previously applied ratings by this user in this scheme. + // See RatingNamingConventionsUtil.java for details. + final QName assocQName = ratingNamingConventions.getRatingAssocNameFor(userName, ratingScheme.getName()); List myRatingChildren = nodeService.getChildAssocs(targetNode, ContentModel.ASSOC_RATINGS, assocQName); if (myRatingChildren.isEmpty()) { - // There are no previous ratings from this user, so we create a new cm:rating child node. + // There are no previous ratings from this user/scheme combination, so we create a new cm:rating child node. Map ratingProps = new HashMap(); ratingProps.put(ContentModel.PROP_RATING_SCORE, rating); @@ -178,37 +208,62 @@ public class RatingServiceImpl implements RatingService } else { - // There are previous ratings by this user. - if (myRatingChildren.size() > 1 && log.isDebugEnabled()) - { - log.debug(""); - } + // There are previous ratings by this user/ratingScheme combination. NodeRef myPreviousRatingsNode = myRatingChildren.get(0).getChildRef(); - Map existingProps = nodeService.getProperties(myPreviousRatingsNode); - String existingRatingScheme = (String)existingProps.get(ContentModel.PROP_RATING_SCHEME); + Map ratingProps = new HashMap(); + ratingProps.put(ContentModel.PROP_RATING_SCHEME, ratingSchemeName); + ratingProps.put(ContentModel.PROP_RATING_SCORE, rating); + ratingProps.put(ContentModel.PROP_RATED_AT, new Date()); - // If it's a re-rating in the existing scheme, replace. - if (ratingScheme.getName().equals(existingRatingScheme)) + nodeService.setProperties(myPreviousRatingsNode, ratingProps); + } + + // Now that we have applied the rating, we need to recalculate the rollup properties. + recalculateRatingRollups(targetNode, ratingScheme); + } + + private void recalculateRatingRollups(NodeRef targetNode, + final RatingScheme ratingScheme) + { + QName rollupAspectName = ratingNamingConventions.getRollupAspectNameFor(ratingScheme); + AspectDefinition rollupAspect = dictionaryService.getAspect(rollupAspectName); + + // Only run the rating rollups for this node if the aspect which will hold the results has been defined + if ((rollupAspect != null)) + { + behaviourFilter.disableBehaviour(targetNode, ContentModel.ASPECT_AUDITABLE); + try { - Map ratingProps = new HashMap(); - ratingProps.put(ContentModel.PROP_RATING_SCHEME, ratingSchemeName); - ratingProps.put(ContentModel.PROP_RATING_SCORE, rating); - ratingProps.put(ContentModel.PROP_RATED_AT, new Date()); - - nodeService.setProperties(myPreviousRatingsNode, ratingProps); + for (AbstractRatingRollupAlgorithm rollupAlgorithm : ratingScheme.getPropertyRollups()) + { + Serializable s = rollupAlgorithm.recalculate(targetNode); + QName rollupPropertyName = ratingNamingConventions.getRollupPropertyNameFor(ratingScheme, rollupAlgorithm.getRollupName()); + nodeService.setProperty(targetNode, rollupPropertyName, s); + + if (!rollupAspect.getProperties().containsKey(rollupPropertyName) && log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Rating property rollup property ").append(rollupPropertyName) + .append(" on aspect " ).append(rollupAspectName) + .append(" is not defined in the content model."); + log.debug(msg.toString()); + } + } } - // But if it's a new rating in a different scheme, we don't support this scenario. - else + finally + { + behaviourFilter.enableBehaviour(targetNode, ContentModel.ASPECT_AUDITABLE); + } + } + else + { + if (log.isDebugEnabled()) { StringBuilder msg = new StringBuilder(); - msg.append("Cannot apply rating ") - .append(rating).append(" [") - .append(ratingSchemeName).append("] to node ") - .append(targetNode).append(". Already rated in ") - .append(existingRatingScheme); - - throw new RatingServiceException(msg.toString()); + msg.append("Rating property rollup aspect ").append(rollupAspectName) + .append(" is not defined in the content model & therefore the rollup was not persisted."); + log.debug(msg.toString()); } } } @@ -223,9 +278,34 @@ public class RatingServiceImpl implements RatingService return this.getRating(targetNode, ratingSchemeName, currentUser); } + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rating.RatingService#getRatingsByCurrentUser(org.alfresco.service.cmr.repository.NodeRef) + */ + public List getRatingsByCurrentUser(NodeRef targetNode) + { + final String fullyAuthenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser(); + + List children = getRatingNodeChildren(targetNode, null, fullyAuthenticatedUser); + List result = new ArrayList(children.size()); + + for (ChildAssociationRef child : children) + { + result.add(convertNodeRefToRating(fullyAuthenticatedUser, child.getChildRef())); + } + return result; + } + + /** + * This method gets the rating for the specified node, in the specified scheme by the specified user. + * @param targetNode the node whose rating we are looking for. + * @param ratingSchemeName the rating scheme name in which we are looking for a rating. + * @param user the user name of the user whose rating we are looking for. + * @return the {@link Rating} if there is one. + */ private Rating getRating(NodeRef targetNode, String ratingSchemeName, String user) { - List ratingChildren = getRatingNodeChildren(targetNode, user); + List ratingChildren = getRatingNodeChildren(targetNode, ratingSchemeName, user); // If there are none, return null if (ratingChildren.isEmpty()) @@ -233,28 +313,34 @@ public class RatingServiceImpl implements RatingService return null; } - // Take the node pertaining to the current user. + // Take the node pertaining to the current user & scheme. ChildAssociationRef ratingNodeAssoc = ratingChildren.get(0); - Map properties = nodeService.getProperties(ratingNodeAssoc.getChildRef()); - // Find the index of the rating scheme we're interested in. + return convertNodeRefToRating(user, ratingNodeAssoc.getChildRef()); + } + + /** + * This method converts a NodeRef (which must be an instance of a cm:rating node) + * into a {@link Rating} object. + * @param ratingSchemeName + * @param user + * @param ratingNode + * @return + */ + private Rating convertNodeRefToRating(String user, NodeRef ratingNode) + { + Map properties = nodeService.getProperties(ratingNode); + String existingRatingScheme = (String)properties.get(ContentModel.PROP_RATING_SCHEME); - if (existingRatingScheme.equals(ratingSchemeName) == false) - { - // There is no rating in this scheme by the specified user. - return null; - } - else - { - Float existingRatingScore = (Float)properties.get(ContentModel.PROP_RATING_SCORE); - Date existingRatingDate = (Date)properties.get(ContentModel.PROP_RATED_AT); - - Rating result = new Rating(getRatingScheme(existingRatingScheme), - existingRatingScore, - user, - existingRatingDate); - return result; - } + + Float existingRatingScore = (Float)properties.get(ContentModel.PROP_RATING_SCORE); + Date existingRatingDate = (Date)properties.get(ContentModel.PROP_RATED_AT); + + Rating result = new Rating(getRatingScheme(existingRatingScheme), + existingRatingScore, + user, + existingRatingDate); + return result; } /* @@ -270,7 +356,7 @@ public class RatingServiceImpl implements RatingService private Rating removeRating(NodeRef targetNode, String ratingSchemeName, final String user) { - List ratingChildren = getRatingNodeChildren(targetNode, user); + List ratingChildren = getRatingNodeChildren(targetNode, ratingSchemeName, user); if (ratingChildren.isEmpty()) { return null; @@ -287,6 +373,9 @@ public class RatingServiceImpl implements RatingService Date date = (Date)properties.get(ContentModel.PROP_RATED_AT); nodeService.deleteNode(child.getChildRef()); + + recalculateRatingRollups(targetNode, getRatingScheme(ratingSchemeName)); + result = new Rating(getRatingScheme(ratingSchemeName), score, user, date); } @@ -299,97 +388,73 @@ public class RatingServiceImpl implements RatingService */ public float getTotalRating(NodeRef targetNode, String ratingSchemeName) { - //TODO Performance improvement? : put node rating total/count/average into a - // property in the db. - List ratingsNodes = this.getRatingNodeChildren(targetNode, null); - - // It's one node per user so the size of this list is the number of ratings applied. - // However not all of these users' ratings need be in the specified scheme. - // So we need to go through and check that the rating node contains a rating for the - // specified scheme. - float result = 0; - for (ChildAssociationRef ratingsNode : ratingsNodes) + Serializable result = this.getRatingRollup(targetNode, ratingSchemeName, RatingTotalRollupAlgorithm.ROLLUP_NAME); + if (result == null) { - Rating rating = getRatingFrom(ratingsNode.getChildRef()); - if (rating.getScheme().getName().equals(ratingSchemeName)) - { - result += rating.getScore(); - } + result = new Float(0f); } - return result; + + return (Float)result; } public float getAverageRating(NodeRef targetNode, String ratingSchemeName) { - List ratingsNodes = this.getRatingNodeChildren(targetNode, null); + float totalRating = getTotalRating(targetNode, ratingSchemeName); + int ratingCount = getRatingsCount(targetNode, ratingSchemeName); - // It's one node per user so the size of this list is the number of ratings applied. - // However not all of these users' ratings need be in the specified scheme. - // So we need to go through and check that the rating node contains a rating for the - // specified scheme. - int ratingCount = 0; - float ratingTotal = 0; - for (ChildAssociationRef ratingsNode : ratingsNodes) - { - Rating rating = getRatingFrom(ratingsNode.getChildRef()); - if (rating.getScheme().getName().equals(ratingSchemeName)) - { - ratingCount++; - ratingTotal += rating.getScore(); - } - } - if (ratingCount == 0) - { - return -1; - } - else - { - return (float)ratingTotal / (float)ratingCount; - } + return ratingCount == 0 ? -1f : totalRating / (float)ratingCount; } public int getRatingsCount(NodeRef targetNode, String ratingSchemeName) { - List ratingsNodes = this.getRatingNodeChildren(targetNode, null); - - // It's one node per user so the size of this list is the number of ratings applied. - // However not all of these users' ratings need be in the specified scheme. - // So we need to go through and check that the rating node contains a rating for the - // specified scheme. - int result = 0; - for (ChildAssociationRef ratingsNode : ratingsNodes) + Serializable result = this.getRatingRollup(targetNode, ratingSchemeName, RatingCountRollupAlgorithm.ROLLUP_NAME); + if (result == null) { - Rating rating = getRatingFrom(ratingsNode.getChildRef()); - if (rating.getScheme().getName().equals(ratingSchemeName)) - { - result++; - } + result = new Integer(0); } + + return (Integer)result; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rating.RatingService#getRatingRollup(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String) + */ + public Serializable getRatingRollup(NodeRef targetNode, String ratingSchemeName, String ratingRollupName) + { + RatingScheme scheme = schemeRegistry.getRatingSchemes().get(ratingSchemeName); + if (scheme == null) + { + throw new RatingServiceException("Cannot retrieve rollup. Unrecognized rating scheme " + ratingSchemeName); + } + + QName rollupAspectName = ratingNamingConventions.getRollupAspectNameFor(ratingSchemeName); + + Serializable result = null; + // If the rated node has the rollup aspect applied + if (nodeService.hasAspect(targetNode, rollupAspectName)) + { + QName rollupPropertyName = ratingNamingConventions.getRollupPropertyNameFor(ratingSchemeName, ratingRollupName); + result = nodeService.getProperty(targetNode, rollupPropertyName); + } + return result; } - /** * This method gets all the cm:rating child nodes of the specified targetNode that - * have been applied by the specified user. + * have been applied by the specified user in the specified rating scheme. * * @param targetNode the target node under which the cm:rating nodes reside. * @param user the user name of the user whose ratings are sought, null * for all users. + * @param ratingSchemeName the name of the rating scheme, null for all schemes. * @return */ - private List getRatingNodeChildren(NodeRef targetNode, - String user) + List getRatingNodeChildren(NodeRef targetNode, + String ratingSchemeName, String user) { - QNamePattern qnamePattern = null; - if (user != null) - { - qnamePattern = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, user); - } - else - { - qnamePattern = RegexQNamePattern.MATCH_ALL; - } + QNamePattern qnamePattern = ratingNamingConventions.getRatingAssocPatternForUser(user, ratingSchemeName); List results = nodeService.getChildAssocs(targetNode, ContentModel.ASSOC_RATINGS, qnamePattern); return results; @@ -400,7 +465,7 @@ public class RatingServiceImpl implements RatingService * @param ratingNode * @return */ - private Rating getRatingFrom(NodeRef ratingNode) + Rating getRatingFrom(NodeRef ratingNode) { // The appliedBy is encoded in the parent assoc qname. // It will be the same user for all ratings in this node. diff --git a/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java b/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java index 06eb91052a..d5a17a99c6 100644 --- a/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/rating/RatingServiceIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -56,7 +56,8 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest private RatingService ratingService; private Repository repositoryHelper; private ScriptService scriptService; -// private RetryingTransactionHelper transactionHelper; + private RatingNamingConventionsUtil ratingNamingConventions; + private NodeRef companyHome; // These NodeRefs are used by the test methods. @@ -81,6 +82,7 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest this.repositoryHelper = (Repository) this.applicationContext.getBean("repositoryHelper"); // this.transactionHelper = (RetryingTransactionHelper) this.applicationContext.getBean("retryingTransactionHelper"); this.scriptService = (ScriptService) this.applicationContext.getBean("scriptService"); + this.ratingNamingConventions = (RatingNamingConventionsUtil)this.applicationContext.getBean("rollupNamingConventions"); // Set the current security context as admin AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); @@ -187,8 +189,8 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest assertEquals("Wrong number of ratings nodes.", 1, allChildren.size()); // child-assoc of type cm:ratings assertEquals("Wrong type qname on ratings assoc", ContentModel.ASSOC_RATINGS, allChildren.get(0).getTypeQName()); - // child-assoc of name cm: - QName expectedAssocName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, AuthenticationUtil.getFullyAuthenticatedUser()); + // child-assoc of name cm: + QName expectedAssocName = ratingNamingConventions.getRatingAssocNameFor(AuthenticationUtil.getFullyAuthenticatedUser(), FIVE_STAR_SCHEME_NAME); assertEquals("Wrong qname on ratings assoc", expectedAssocName, allChildren.get(0).getQName()); // node structure seems ok. @@ -250,8 +252,6 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest */ private void assertDateIsCloseToNow(Date d) { - //TODO Turning this assertion off temporarily - // assertNotNull("Date was unexpectedly null", d); // Date now = new Date(); // assertTrue("Date was not before 'now'", now.after(d)); @@ -282,39 +282,23 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest /** * This test method ensures that if a single user attempts to rate a piece of content in two - * different rating schemes, then an exception should be thrown. - * @throws Exception + * different rating schemes, then an exception should not be thrown. */ public void testOneUserRatesInTwoSchemes() throws Exception { AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); - ratingService.applyRating(testDoc_Admin, 1.0f, FIVE_STAR_SCHEME_NAME); + ratingService.applyRating(testDoc_Admin, 2.0f, FIVE_STAR_SCHEME_NAME); - // A new score in a different rating scheme by the same user should fail. - boolean correctExceptionThrown = false; - try - { - ratingService.applyRating(testDoc_Admin, 2.0f, LIKES_SCHEME_NAME); - } catch (RatingServiceException expected) - { - correctExceptionThrown = true; - } - if (correctExceptionThrown == false) - { - fail("Expected exception not thrown."); - } + // A new score in a different rating scheme by the same user should not fail. + ratingService.applyRating(testDoc_Admin, 1.0f, LIKES_SCHEME_NAME); - float meanRating = ratingService.getAverageRating(testDoc_Admin, FIVE_STAR_SCHEME_NAME); - assertEquals("Document had wrong mean rating.", 1f, meanRating); - - float totalRating = ratingService.getTotalRating(testDoc_Admin, FIVE_STAR_SCHEME_NAME); - assertEquals("Document had wrong total rating.", 1f, totalRating); - - int ratingsCount = ratingService.getRatingsCount(testDoc_Admin, FIVE_STAR_SCHEME_NAME); - assertEquals("Document had wrong ratings count.", 1, ratingsCount); + // There should be two rating child nodes under the rated node. + assertEquals("Wrong number of child nodes", 2 , nodeService.getChildAssocs(testDoc_Admin).size()); - // There should only be one rating child node under the rated node. - assertEquals("Wrong number of child nodes", 1 , nodeService.getChildAssocs(testDoc_Admin).size()); + List ratings = ratingService.getRatingsByCurrentUser(testDoc_Admin); + assertEquals(2, ratings.size()); + assertEquals(FIVE_STAR_SCHEME_NAME, ratings.get(0).getScheme().getName()); + assertEquals(LIKES_SCHEME_NAME, ratings.get(1).getScheme().getName()); } /** @@ -343,6 +327,22 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest int ratingsCount = ratingService.getRatingsCount(testDoc_Admin, FIVE_STAR_SCHEME_NAME); assertEquals("Document had wrong ratings count.", 2, ratingsCount); + + assertModifierIs(testDoc_Admin, AuthenticationUtil.getAdminUserName()); + + // One user removes their rating. + ratingService.removeRatingByCurrentUser(testDoc_Admin, FIVE_STAR_SCHEME_NAME); + + meanRating = ratingService.getAverageRating(testDoc_Admin, FIVE_STAR_SCHEME_NAME); + assertEquals("Document had wrong mean rating.", 4f, meanRating); + + totalRating = ratingService.getTotalRating(testDoc_Admin, FIVE_STAR_SCHEME_NAME); + assertEquals("Document had wrong total rating.", 4f, totalRating); + + ratingsCount = ratingService.getRatingsCount(testDoc_Admin, FIVE_STAR_SCHEME_NAME); + assertEquals("Document had wrong ratings count.", 1, ratingsCount); + + assertModifierIs(testDoc_Admin, AuthenticationUtil.getAdminUserName()); } /** @@ -359,9 +359,13 @@ public class RatingServiceIntegrationTest extends BaseAlfrescoSpringTest public void testUsersCantRateTheirOwnContent() throws Exception { + // In the likes rating scheme, users can rate their own content. + AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); + ratingService.applyRating(testDoc_UserTwo, 1, LIKES_SCHEME_NAME); + try { - AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); + // But fiveStar rating scheme disallows rating your own content. ratingService.applyRating(testDoc_UserTwo, 4, FIVE_STAR_SCHEME_NAME); } catch (RatingServiceException expected) { diff --git a/source/java/org/alfresco/repo/rating/RatingTotalRollupAlgorithm.java b/source/java/org/alfresco/repo/rating/RatingTotalRollupAlgorithm.java new file mode 100644 index 0000000000..52f8382769 --- /dev/null +++ b/source/java/org/alfresco/repo/rating/RatingTotalRollupAlgorithm.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2005-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 . + */ + +package org.alfresco.repo.rating; + +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.rating.Rating; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * An implementation of {@link AbstractRatingRollupAlgorithm} which calculates the total (sum) of all + * ratings in a given scheme. + * + * @author Neil McErlean + * @since 3.5 + */ +public class RatingTotalRollupAlgorithm extends AbstractRatingRollupAlgorithm +{ + public static final String ROLLUP_NAME = "Total"; + + public RatingTotalRollupAlgorithm() + { + super(ROLLUP_NAME); + } + + public Float recalculate(NodeRef ratedNode) + { + float result = 0; + + // If the node is not rateable, then it has no ratings in any scheme. + if (nodeService.hasAspect(ratedNode, ContentModel.ASPECT_RATEABLE)) + { + List ratingsNodes = ratingServiceImpl.getRatingNodeChildren(ratedNode, ratingSchemeName, null); + // Filter by scheme + for (ChildAssociationRef chAssRef : ratingsNodes) + { + NodeRef nextRatingNode = chAssRef.getChildRef(); + Rating rating = ratingServiceImpl.getRatingFrom(nextRatingNode); + if (ratingSchemeName.equals(rating.getScheme().getName())) + { + result += rating.getScore(); + } + } + } + + return result; + } +} diff --git a/source/java/org/alfresco/repo/rating/script/ScriptRatingService.java b/source/java/org/alfresco/repo/rating/script/ScriptRatingService.java index ef69d05542..03967ef833 100644 --- a/source/java/org/alfresco/repo/rating/script/ScriptRatingService.java +++ b/source/java/org/alfresco/repo/rating/script/ScriptRatingService.java @@ -88,6 +88,19 @@ public class ScriptRatingService extends BaseScopableProcessorExtension { return ratingService.getRatingScheme(ratingSchemeName).getMaxRating(); } + + /** + * This method checks whether self-rating is allowed for the specified rating scheme. + * If self-rating is allowed in the specified scheme, then the cm:creator of a node can apply a rating, + * otherwise they cannot. + * + * @param ratingSchemeName the rating scheme bean name. + * @return true if users can rate their own content, else false. + */ + public boolean isSelfRatingAllowed(String ratingSchemeName) + { + return ratingService.getRatingScheme(ratingSchemeName).isSelfRatingAllowed(); + } /** * Applies the given rating to the specified node using the specified ratingScheme. diff --git a/source/java/org/alfresco/repo/rating/script/test_ratingService.js b/source/java/org/alfresco/repo/rating/script/test_ratingService.js index 30a2c97345..38201a300b 100644 --- a/source/java/org/alfresco/repo/rating/script/test_ratingService.js +++ b/source/java/org/alfresco/repo/rating/script/test_ratingService.js @@ -7,9 +7,11 @@ function testRatingSchemes() test.assertEquals(1, ratingService.getMin('likesRatingScheme')); test.assertEquals(1, ratingService.getMax('likesRatingScheme')); + test.assertEquals(true, ratingService.isSelfRatingAllowed('likesRatingScheme')); test.assertEquals(1, ratingService.getMin('fiveStarRatingScheme')); test.assertEquals(5, ratingService.getMax('fiveStarRatingScheme')); + test.assertEquals(false, ratingService.isSelfRatingAllowed('fiveStarRatingScheme')); } function testApplyUpdateDeleteRatings() @@ -22,7 +24,7 @@ function testApplyUpdateDeleteRatings() // Now apply some ratings. ratingService.applyRating(testNode, 2.0, 'fiveStarRatingScheme'); test.assertEquals(2.0, ratingService.getRating(testNode, 'fiveStarRatingScheme')); - //TODO Date formats in JS API? + test.assertNotNull(ratingService.getRatingAppliedAt(testNode, 'fiveStarRatingScheme')); test.assertEquals(1, ratingService.getRatingsCount(testNode, 'fiveStarRatingScheme')); @@ -33,7 +35,7 @@ function testApplyUpdateDeleteRatings() // And update them ratingService.applyRating(testNode, 4.5, 'fiveStarRatingScheme'); test.assertEquals(4.5, ratingService.getRating(testNode, 'fiveStarRatingScheme')); - //TODO Date formats in JS API? + test.assertNotNull(ratingService.getRatingAppliedAt(testNode, 'fiveStarRatingScheme')); test.assertEquals(1, ratingService.getRatingsCount(testNode, 'fiveStarRatingScheme')); diff --git a/source/java/org/alfresco/repo/rendition/AllRenditionTests.java b/source/java/org/alfresco/repo/rendition/AllRenditionTests.java index 2940940ef2..d3c804f376 100644 --- a/source/java/org/alfresco/repo/rendition/AllRenditionTests.java +++ b/source/java/org/alfresco/repo/rendition/AllRenditionTests.java @@ -22,6 +22,7 @@ import org.alfresco.repo.rendition.executer.AbstractRenderingEngineTest; import org.alfresco.repo.rendition.executer.HTMLRenderingEngineTest; import org.alfresco.repo.thumbnail.ThumbnailServiceImplParameterTest; import org.alfresco.repo.thumbnail.ThumbnailServiceImplTest; +import org.alfresco.repo.thumbnail.conditions.NodeEligibleForRethumbnailingEvaluatorTest; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -38,6 +39,7 @@ import org.junit.runners.Suite; AbstractRenderingEngineTest.class, ThumbnailServiceImplParameterTest.class, ThumbnailServiceImplTest.class, + NodeEligibleForRethumbnailingEvaluatorTest.class, StandardRenditionLocationResolverTest.class, RenditionServiceIntegrationTest.class, RenditionServicePermissionsTest.class, diff --git a/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java b/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java index bfdf469b71..fedc0efc2d 100644 --- a/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java +++ b/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java @@ -26,12 +26,14 @@ import org.alfresco.cmis.CMISDictionaryService; import org.alfresco.cmis.CMISQueryService; import org.alfresco.cmis.CMISServices; import org.alfresco.mbeans.VirtServerRegistry; +import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.forms.FormService; import org.alfresco.repo.imap.ImapService; import org.alfresco.repo.lock.JobLockService; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.avm.AVMService; @@ -498,4 +500,20 @@ public class MockedTestServiceRegistry implements ServiceRegistry // TODO Auto-generated method stub return null; } + + + @Override + public RepoAdminService getRepoAdminService() + { + // TODO Auto-generated method stub + return null; + } + + + @Override + public SysAdminParams getSysAdminParams() + { + // TODO Auto-generated method stub + return null; + } } diff --git a/source/java/org/alfresco/repo/rendition/MultiUserRenditionTest.java b/source/java/org/alfresco/repo/rendition/MultiUserRenditionTest.java index 848646c9f2..a39762c309 100644 --- a/source/java/org/alfresco/repo/rendition/MultiUserRenditionTest.java +++ b/source/java/org/alfresco/repo/rendition/MultiUserRenditionTest.java @@ -176,14 +176,14 @@ public class MultiUserRenditionTest this.nodesToBeTidiedUp.add(nonAdminPdfNode); // Now have each user create a rendition (thumbnail) of the other user's content node. - final RenditionDefinition doclibRD = renditionService.loadRenditionDefinition(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib")); + final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER); txnHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { public Void execute() throws Throwable { - renditionService.render(nonAdminPdfNode, doclibRD); + renditionService.render(nonAdminPdfNode, doclibRendDefQName); return null; } }); @@ -193,7 +193,7 @@ public class MultiUserRenditionTest { public Void execute() throws Throwable { - renditionService.render(adminPdfNode, doclibRD); + renditionService.render(adminPdfNode, doclibRendDefQName); return null; } }); diff --git a/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java index 206910c568..2eb4fe9401 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java +++ b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java @@ -25,7 +25,10 @@ import org.alfresco.service.cmr.rendition.RenditionDefinition; import org.alfresco.service.namespace.QName; /** - * This class provides the implementation of RenditionDefinition persistence. + * This class provides the implementation of {@link RenditionDefinition} persistence. + *

    + * Note that rendition definitions are saved underneath the Data Dictionary and therefore any code + * which loads or saves rendition definitions must have the appropriate authorisation. * * @author Nick Smith * @author Neil McErlean diff --git a/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java index 405818aac0..0cbf825c47 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java +++ b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java @@ -39,6 +39,12 @@ import org.alfresco.service.namespace.QName; /** * This class provides the implementation of RenditionDefinition persistence. + *

    + * N.B. {@link RenditionDefinition Rendition definitions} are stored within the Data Dictionary in the + * Alfresco Repository & therefore calls to load or save definitions will be subject to the normal + * authorisation checks for those nodes. In particular this means that if the Data Dictionary has been + * given restricted access control (it is Consumer for Group ALL by default), it may not be possible for + * normal users to load rendition definitions. * * @author Nick Smith * @author Neil McErlean diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java b/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java index adbfe4af2f..d54059b8ad 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java @@ -28,6 +28,7 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.model.RenditionModel; import org.alfresco.repo.action.executer.ActionExecuter; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.ActionDefinition; import org.alfresco.service.cmr.action.ActionService; @@ -200,6 +201,53 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti return; } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionService#render(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) + */ + public ChildAssociationRef render(NodeRef sourceNode, final QName renditionDefinitionQName) + { + RenditionDefinition rendDefn = AuthenticationUtil.runAs( + new AuthenticationUtil.RunAsWork() + { + public RenditionDefinition doWork() throws Exception + { + return loadRenditionDefinition(renditionDefinitionQName); + } + }, AuthenticationUtil.getSystemUserName()); + + if (rendDefn == null) + { + throw new RenditionServiceException("Rendition Definition " + renditionDefinitionQName + " was not found."); + } + + return this.render(sourceNode, rendDefn); + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionService#render(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.cmr.rendition.RenderCallback) + */ + public void render(NodeRef sourceNode, final QName renditionDefinitionQName, RenderCallback callback) + { + RenditionDefinition rendDefn = AuthenticationUtil.runAs( + new AuthenticationUtil.RunAsWork() + { + public RenditionDefinition doWork() throws Exception + { + return loadRenditionDefinition(renditionDefinitionQName); + } + }, AuthenticationUtil.getSystemUserName()); + + if (rendDefn == null) + { + throw new RenditionServiceException("Rendition Definition " + renditionDefinitionQName + " was not found."); + } + + this.render(sourceNode, rendDefn, callback); + } + /** * This method delegates the execution of the specified RenditionDefinition @@ -229,7 +277,7 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti .append(" with ").append(definition.getRenditionName()); log.debug(msg.toString()); } - final boolean checkConditions = false; + final boolean checkConditions = true; actionService.executeAction(definition, sourceNode, checkConditions, asynchronous); ChildAssociationRef result = (ChildAssociationRef)definition.getParameterValue(ActionExecuter.PARAM_RESULT); diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java index afd4474e78..111fffd068 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -21,6 +21,7 @@ package org.alfresco.repo.rendition; import java.awt.image.BufferedImage; import java.io.File; +import java.io.InputStream; import java.io.Serializable; import java.net.URL; import java.util.ArrayList; @@ -1660,6 +1661,90 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest assertEquals(MimetypeMap.MIMETYPE_IMAGE_PNG, avatarRenditionDef.getParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE)); } + /** + * This test checks that for a node with an existing rendition, that if you update its content with content + * that cannot be renditioned (thumbnailed), that existing rendition nodes for failed re-renditions are removed. + * See ALF-6730. + * + * @since 3.4.2 + */ + public void testRenderValidContentThenUpdateToInvalidContent() throws Exception + { + setComplete(); + endTransaction(); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // Need to use one of the standard, persisted rendition definitions, as non-persisted rendition definitions + // do not get automatcially updated. + RenditionDefinition doclib = loadAndValidateRenditionDefinition("doclib"); + + // We want the rendition to be asynchronous, but we don't care about called-back data. + RenderCallback dummyCallback = new RenderCallback(){ + @Override + public void handleFailedRendition(Throwable t) + { + } + @Override + public void handleSuccessfulRendition( + ChildAssociationRef primaryParentOfNewRendition) + { + }}; + renditionService.render(nodeWithDocContent, doclib, dummyCallback); + return null; + } + }); + + // Now wait for the actionService thread to complete the rendering. + Thread.sleep(5000); + + // The rendition should have succeeded. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + List renditions = renditionService.getRenditions(nodeWithDocContent); + assertNotNull("Renditions missing.", renditions); + assertEquals("Wrong rendition count", 1, renditions.size()); + return null; + } + }); + + // Now update the content to a corrupt PDF file that cannot be rendered. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + ContentWriter writer = contentService.getWriter(nodeWithDocContent, ContentModel.PROP_CONTENT, true); + writer.setMimetype(MimetypeMap.MIMETYPE_PDF); + writer.setEncoding("UTF-8"); + + InputStream corruptIn = applicationContext.getResource("classpath:/quick/quickCorrupt.pdf").getInputStream(); + writer.putContent(corruptIn); + corruptIn.close(); + + return null; + } + }); + + // Now wait for the actionService thread to complete the rendition removal. + Thread.sleep(5000); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + List renditions = renditionService.getRenditions(nodeWithDocContent); + assertNotNull("Renditions missing.", renditions); + assertEquals("Wrong rendition count", 0, renditions.size()); + return null; + } + }); + } + + public void testALF3733() throws Exception { // ALF-3733 was caused by ${cwd} evaluating to the empty string and a path "//sourceNodeName" diff --git a/source/java/org/alfresco/repo/rendition/RenditionedAspect.java b/source/java/org/alfresco/repo/rendition/RenditionedAspect.java index 468b69ab39..aa1e8a9085 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionedAspect.java +++ b/source/java/org/alfresco/repo/rendition/RenditionedAspect.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -33,6 +33,10 @@ import org.alfresco.repo.policy.Behaviour; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; +import org.alfresco.repo.rendition.executer.DeleteRenditionActionExecuter; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -66,11 +70,12 @@ public class RenditionedAspect implements NodeServicePolicies.OnUpdateProperties private static final Log logger = LogFactory.getLog(RenditionedAspect.class); /** Services */ - private PolicyComponent policyComponent; - private NodeService nodeService; + private ActionService actionService; private DictionaryService dictionaryService; + private NodeService nodeService; + private PolicyComponent policyComponent; private RenditionService renditionService; - + /** * Set the policy component * @@ -81,6 +86,14 @@ public class RenditionedAspect implements NodeServicePolicies.OnUpdateProperties this.policyComponent = policyComponent; } + /** + * @since 3.4.2 + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + /** * Set the node service * @@ -119,7 +132,7 @@ public class RenditionedAspect implements NodeServicePolicies.OnUpdateProperties this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), RenditionModel.ASPECT_RENDITIONED, - new JavaBehaviour(this, "onUpdateProperties", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); + new JavaBehaviour(this, "onUpdateProperties", Behaviour.NotificationFrequency.EVERY_EVENT)); this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), RenditionModel.ASPECT_RENDITIONED, @@ -143,8 +156,19 @@ public class RenditionedAspect implements NodeServicePolicies.OnUpdateProperties List renditions = this.renditionService.getRenditions(nodeRef); for (ChildAssociationRef chAssRef : renditions) { - QName renditionAssocName = chAssRef.getQName(); - RenditionDefinition rendDefn = this.renditionService.loadRenditionDefinition(renditionAssocName); + final QName renditionAssocName = chAssRef.getQName(); + + // Rendition Definitions are persisted underneath the Data Dictionary for which Group ALL + // has Consumer access by default. However, we cannot assume that that access level applies for all deployments. See ALF-7334. + RenditionDefinition rendDefn = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + @Override + public RenditionDefinition doWork() throws Exception + { + return renditionService.loadRenditionDefinition(renditionAssocName); + } + }, AuthenticationUtil.getSystemUserName()); + if (rendDefn == null) { if (logger.isDebugEnabled()) @@ -252,6 +276,10 @@ public class RenditionedAspect implements NodeServicePolicies.OnUpdateProperties if (rendDefn != null) { + Action deleteRendition = actionService.createAction(DeleteRenditionActionExecuter.NAME); + deleteRendition.setParameterValue(DeleteRenditionActionExecuter.PARAM_RENDITION_DEFINITION_NAME, rendDefn.getRenditionName()); + rendDefn.setCompensatingAction(deleteRendition); + renditionService.render(sourceNodeRef, rendDefn, new RenderCallback() { public void handleFailedRendition(Throwable t) @@ -269,11 +297,6 @@ public class RenditionedAspect implements NodeServicePolicies.OnUpdateProperties .append("and does not affect operation of the system."); logger.debug(msg.toString(), t); } - - if (nodeService.exists(renditionAssoc.getChildRef())) - { - nodeService.deleteNode(renditionAssoc.getChildRef()); - } } public void handleSuccessfulRendition(ChildAssociationRef primaryParentOfNewRendition) diff --git a/source/java/org/alfresco/repo/rendition/executer/BaseTemplateRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/BaseTemplateRenderingEngine.java index 37ff53e443..0ddb6859b6 100644 --- a/source/java/org/alfresco/repo/rendition/executer/BaseTemplateRenderingEngine.java +++ b/source/java/org/alfresco/repo/rendition/executer/BaseTemplateRenderingEngine.java @@ -239,6 +239,8 @@ public abstract class BaseTemplateRenderingEngine extends AbstractRenderingEngin DataTypeDefinition.NODE_REF, false, getParamDisplayLabel(PARAM_TEMPLATE_NODE)); ParameterDefinitionImpl templatePathParamDef = new ParameterDefinitionImpl(PARAM_TEMPLATE_PATH, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_TEMPLATE_PATH)); + paramList.add(new ParameterDefinitionImpl(PARAM_MIME_TYPE, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_MIME_TYPE))); paramList.add(modelParamDef); paramList.add(templateParamDef); paramList.add(templateNodeParamDef); diff --git a/source/java/org/alfresco/repo/rendition/executer/DeleteRenditionActionExecuter.java b/source/java/org/alfresco/repo/rendition/executer/DeleteRenditionActionExecuter.java new file mode 100644 index 0000000000..fb83f7e761 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/executer/DeleteRenditionActionExecuter.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.repo.rendition.executer; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.executer.ActionExecuter; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.rendition.RenditionedAspect; +import org.alfresco.repo.thumbnail.AddFailedThumbnailActionExecuter; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +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.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This {@link ActionExecuter} implementation is used internally to delete rendition nodes when a rendition update has failed. + * The scenario is as follows: a content node exists in the repository and has a number of rendition nodes associated with it. + * When the content node is given new content, each of the rendition nodes must be updated to reflect the new source content. + * But if one or more of those re-renditions fail, then the old rendition nodes now refer to out of date content and should be deleted. + *

    + * This class executes the deletion of the specified rendition node. + * + * @author Neil Mc Erlean + * @since 3.4.2 + * + * @see RenditionedAspect + * @see AddFailedThumbnailActionExecuter + */ +public class DeleteRenditionActionExecuter extends ActionExecuterAbstractBase +{ + private static Log log = LogFactory.getLog(DeleteRenditionActionExecuter.class); + + /** + * The action bean name. + */ + public static final String NAME = "delete-rendition"; + + /** + * The name of the rendition definition to delete e.g. cm:doclib. + */ + public static final String PARAM_RENDITION_DEFINITION_NAME = "rendition-definition-name"; + + private NodeService nodeService; + private RenditionService renditionService; + private BehaviourFilter behaviourFilter; + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setRenditionService(RenditionService renditionService) + { + this.renditionService = renditionService; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(org.alfresco.service.cmr.repository.NodeRef, NodeRef) + */ + public void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + final boolean nodeExists = this.nodeService.exists(actionedUponNodeRef); + if (nodeExists) + { + Map paramValues = action.getParameterValues(); + final QName renditionDefName = (QName)paramValues.get(PARAM_RENDITION_DEFINITION_NAME); + + ChildAssociationRef existingRendition = renditionService.getRenditionByName(actionedUponNodeRef, renditionDefName); + + if (existingRendition != null) + { + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Deleting rendition node: ").append(existingRendition); + log.debug(msg.toString()); + } + + behaviourFilter.disableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); + try + { + nodeService.deleteNode(existingRendition.getChildRef()); + } + finally + { + behaviourFilter.enableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); + } + } + } + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_RENDITION_DEFINITION_NAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_RENDITION_DEFINITION_NAME), false)); + } +} diff --git a/source/java/org/alfresco/repo/rendition/script/ScriptRenditionService.java b/source/java/org/alfresco/repo/rendition/script/ScriptRenditionService.java index 00d10f3b2b..d3bdf0fa4d 100644 --- a/source/java/org/alfresco/repo/rendition/script/ScriptRenditionService.java +++ b/source/java/org/alfresco/repo/rendition/script/ScriptRenditionService.java @@ -22,6 +22,7 @@ import java.util.List; import org.alfresco.repo.jscript.BaseScopableProcessorExtension; import org.alfresco.repo.jscript.ScriptNode; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.rendition.RenderingEngineDefinition; import org.alfresco.service.cmr.rendition.RenditionDefinition; @@ -58,11 +59,22 @@ public class ScriptRenditionService extends BaseScopableProcessorExtension private RenditionDefinition loadRenditionDefinitionImpl(String shortOrLongFormQName) { - QName renditionName = createQName(shortOrLongFormQName); - RenditionDefinition rendDef = renditionService.loadRenditionDefinition(renditionName); - return rendDef; + final QName renditionName = createQName(shortOrLongFormQName); + + // Rendition Definitions are persisted underneath the Data Dictionary for which Group ALL + // has Consumer access by default. However, we cannot assume that that access level applies for all deployments. See ALF-7334. + RenditionDefinition rendDefn = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + @Override + public RenditionDefinition doWork() throws Exception + { + return renditionService.loadRenditionDefinition(renditionName); + } + }, AuthenticationUtil.getSystemUserName()); + return rendDefn; } + /** * Creates a new {@link ScriptRenditionDefinition} and sets the rendition name and * the rendering engine name to the specified values. diff --git a/source/java/org/alfresco/repo/replication/ReplicationActionExecutor.java b/source/java/org/alfresco/repo/replication/ReplicationActionExecutor.java index bf1f1ed8a9..93ad64cf41 100644 --- a/source/java/org/alfresco/repo/replication/ReplicationActionExecutor.java +++ b/source/java/org/alfresco/repo/replication/ReplicationActionExecutor.java @@ -252,6 +252,10 @@ public class ReplicationActionExecutor extends ActionExecuterAbstractBase { { throw new ReplicationServiceException("Unable to execute a disabled replication definition"); } + if(!replicationParams.isEnabled()) + { + throw new ReplicationServiceException("Unable to replicate. The replication service is not enabled"); + } // Lock the service - only one instance of the replication // should occur at a time diff --git a/source/java/org/alfresco/repo/replication/ReplicationParams.java b/source/java/org/alfresco/repo/replication/ReplicationParams.java index 2a9dfcb40d..6046719af6 100644 --- a/source/java/org/alfresco/repo/replication/ReplicationParams.java +++ b/source/java/org/alfresco/repo/replication/ReplicationParams.java @@ -31,4 +31,16 @@ public interface ReplicationParams * @return true lock replication items */ public boolean getTransferReadOnly(); + + /** + * Is the Replication Service Enabled + * + * @return true the replication service is enabled + */ + public boolean isEnabled(); + + /** + * set whether the replication service is enabled + */ + public void setEnabled(boolean enabled); } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/replication/ReplicationParamsImpl.java b/source/java/org/alfresco/repo/replication/ReplicationParamsImpl.java index 83c9b139fd..17d1d29b0e 100644 --- a/source/java/org/alfresco/repo/replication/ReplicationParamsImpl.java +++ b/source/java/org/alfresco/repo/replication/ReplicationParamsImpl.java @@ -26,6 +26,7 @@ public class ReplicationParamsImpl implements ReplicationParams { /** Lock replication items? */ private boolean readOnly = true; + private boolean isEnabled = true; public ReplicationParamsImpl() { @@ -49,4 +50,15 @@ public class ReplicationParamsImpl implements ReplicationParams { return this.readOnly; } + + @Override + public boolean isEnabled() + { + return isEnabled; + } + + public void setEnabled(boolean isEnabled) + { + this.isEnabled = isEnabled; + } } diff --git a/source/java/org/alfresco/repo/replication/ReplicationServiceImpl.java b/source/java/org/alfresco/repo/replication/ReplicationServiceImpl.java index b3a2dc97d1..75cb45f262 100644 --- a/source/java/org/alfresco/repo/replication/ReplicationServiceImpl.java +++ b/source/java/org/alfresco/repo/replication/ReplicationServiceImpl.java @@ -25,6 +25,7 @@ import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction; import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedActionService; import org.alfresco.service.cmr.replication.ReplicationDefinition; import org.alfresco.service.cmr.replication.ReplicationService; +import org.alfresco.service.cmr.replication.ReplicationServiceException; import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -38,7 +39,7 @@ public class ReplicationServiceImpl implements ReplicationService, ReplicationDe private ActionService actionService; private ScheduledPersistedActionService scheduledPersistedActionService; - + private ReplicationParams replicationParams; private ReplicationDefinitionPersisterImpl replicationDefinitionPersister; /** @@ -184,16 +185,24 @@ public class ReplicationServiceImpl implements ReplicationService, ReplicationDe /* * (non-Javadoc) - * @see + * @see.. * org.alfresco.service.cmr.replication.ReplicationService#replication * (ReplicationDefinition) */ - public void replicate(ReplicationDefinition replicationDefinition) { - actionService.executeAction( - replicationDefinition, - ReplicationDefinitionPersisterImpl.REPLICATION_ACTION_ROOT_NODE_REF - ); - } + public void replicate(ReplicationDefinition replicationDefinition) + { + + if(isEnabled()) + { + actionService.executeAction( + replicationDefinition, + ReplicationDefinitionPersisterImpl.REPLICATION_ACTION_ROOT_NODE_REF); + } + else + { + throw new ReplicationServiceException("Unable to replicate. The replication service is not enabled"); + } + } public void disableScheduling(ReplicationDefinition replicationDefinition) { ReplicationDefinitionImpl definition = (ReplicationDefinitionImpl)replicationDefinition; @@ -214,4 +223,24 @@ public class ReplicationServiceImpl implements ReplicationService, ReplicationDe ((ReplicationDefinitionImpl)replicationDefinition).setSchedule(schedule); } } + + @Override + public boolean isEnabled() + { + if(replicationParams != null) + { + return replicationParams.isEnabled(); + } + return true; + } + + /** + * Sets Replication Parameters + * + * @param replicationParams replication parameters + */ + public void setReplicationParams(ReplicationParams replicationParams) + { + this.replicationParams = replicationParams; + } } diff --git a/source/java/org/alfresco/repo/replication/ReplicationServiceIntegrationTest.java b/source/java/org/alfresco/repo/replication/ReplicationServiceIntegrationTest.java index 2014f1040d..ec682a0d26 100644 --- a/source/java/org/alfresco/repo/replication/ReplicationServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/replication/ReplicationServiceIntegrationTest.java @@ -154,6 +154,8 @@ public class ReplicationServiceIntegrationTest extends TestCase // Set the current security context as admin AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + replicationParams.setEnabled(true); + UserTransaction txn = transactionService.getUserTransaction(); txn.begin(); @@ -500,7 +502,7 @@ public class ReplicationServiceIntegrationTest extends TestCase { // We need the test transfer target for this test makeTransferTarget(); - + // Ensure the destination is empty // (don't want to get confused with older runs) assertEquals(0, nodeService.getChildAssocs(destinationFolder).size()); @@ -1119,8 +1121,15 @@ public class ReplicationServiceIntegrationTest extends TestCase assertEquals(IntervalPeriod.Hour, rd.getScheduleIntervalPeriod()); - // Disable it - replicationService.disableScheduling(rd); + // Disable it + transactionService.getRetryingTransactionHelper().doInTransaction(new DoInTransaction(rd) + { + public Void execute() throws Throwable + { + replicationService.disableScheduling(replicationDefinition); + return null; + } + }); assertEquals(false, rd.isSchedulingEnabled()); diff --git a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index 6420990087..765db6e2da 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -285,8 +285,8 @@ public class RuleServiceCoverageTest extends TestCase ActionServiceImplTest.postAsyncActionTest( this.transactionService, - 1000, - 100, + 5000, + 12, new AsyncTest() { public String executeTest() diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java index 80d4ef6e25..e75d34fda6 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImpl.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImpl.java @@ -51,7 +51,6 @@ import org.alfresco.service.cmr.rule.RuleServiceException; import org.alfresco.service.cmr.rule.RuleType; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.GUID; @@ -70,7 +69,7 @@ import org.springframework.extensions.surf.util.ParameterCheck; */ public class RuleServiceImpl implements RuleService, RuntimeRuleService, - NodeServicePolicies.BeforeCreateChildAssociationPolicy, + NodeServicePolicies.OnCreateChildAssociationPolicy, NodeServicePolicies.OnCreateNodePolicy, NodeServicePolicies.OnUpdateNodePolicy, NodeServicePolicies.OnAddAspectPolicy @@ -227,40 +226,40 @@ public class RuleServiceImpl public void init() { policyComponent.bindAssociationBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "beforeCreateChildAssociation"), + NodeServicePolicies.OnCreateChildAssociationPolicy.QNAME, RuleModel.ASPECT_RULES, RuleModel.ASSOC_RULE_FOLDER, - new JavaBehaviour(this, "beforeCreateChildAssociation")); + new JavaBehaviour(this, "onCreateChildAssociation")); policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), + NodeServicePolicies.OnAddAspectPolicy.QNAME, RuleModel.ASPECT_RULES, new JavaBehaviour(this, "onAddAspect")); policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), + NodeServicePolicies.OnUpdateNodePolicy.QNAME, RuleModel.ASPECT_RULES, new JavaBehaviour(this, "onUpdateNode")); policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), + NodeServicePolicies.OnCreateNodePolicy.QNAME, RuleModel.TYPE_RULE, new JavaBehaviour(this, "onCreateNode")); policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), + NodeServicePolicies.OnUpdateNodePolicy.QNAME, RuleModel.TYPE_RULE, new JavaBehaviour(this, "onUpdateNode")); policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), + NodeServicePolicies.OnCreateNodePolicy.QNAME, ActionModel.TYPE_ACTION_BASE, new JavaBehaviour(this, "onCreateNode")); policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), + NodeServicePolicies.OnUpdateNodePolicy.QNAME, ActionModel.TYPE_ACTION_BASE, new JavaBehaviour(this, "onUpdateNode")); policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), + NodeServicePolicies.OnCreateNodePolicy.QNAME, ActionModel.TYPE_ACTION_PARAMETER, new JavaBehaviour(this, "onCreateNode")); policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), + NodeServicePolicies.OnUpdateNodePolicy.QNAME, ActionModel.TYPE_ACTION_PARAMETER, new JavaBehaviour(this, "onUpdateNode")); } @@ -268,12 +267,8 @@ public class RuleServiceImpl /** * Cache invalidation */ - public void beforeCreateChildAssociation( - NodeRef parentNodeRef, - NodeRef childNodeRef, - QName assocTypeQName, - QName assocQName, - boolean isNewNode) + @Override + public void onCreateChildAssociation(ChildAssociationRef childAssocRef, boolean isNewNode) { nodeRulesCache.clear(); } diff --git a/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java b/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java index 37d155e75b..6cacae1507 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceImplTest.java @@ -891,7 +891,7 @@ public class RuleServiceImplTest extends BaseRuleTest NodeRef testFolderNodeRef = childAssocRef.getChildRef(); // get script nodeRef - NodeRef scriptRef = searchService.selectNodes(storeRootNodeRef, "/app:company_home/app:dictionary/app:scripts/cm:backup.js", null, namespaceService, false).get(0); + NodeRef scriptRef = searchService.selectNodes(storeRootNodeRef, "/app:company_home/app:dictionary/app:scripts/cm:backup.js.sample", null, namespaceService, false).get(0); assertNotNull("NodeRef script is null", scriptRef); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java index 7d36e2c316..136f3e3ac8 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java @@ -852,6 +852,223 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener ResultSet results = serviceRegistry.getSearchService().query(sp); results.close(); } + + public void test_ALF_8007() throws Exception + { + // Check that updates before and after queries do not produce duplicates + + testTX.commit(); + testTX = transactionService.getUserTransaction(); + testTX.begin(); + + this.authenticationComponent.setCurrentUser("admin"); + + SearchParameters sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + ResultSet results; + ResultSetMetaData md; + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(0, results.length()); + results.close(); + + + Map properties = new HashMap(); + + properties.put(ContentModel.PROP_NAME, "ALF-8007"); + NodeRef one = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007"), ContentModel.TYPE_CONTENT, properties).getChildRef(); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(1, results.length()); + results.close(); + + + MLText desc1 = new MLText(); + desc1.addValue(Locale.ENGLISH, "ALF 8007"); + desc1.addValue(Locale.US, "ALF 8007"); + + nodeService.setProperty(one, ContentModel.PROP_DESCRIPTION, desc1); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(1, results.length()); + results.close(); + + // check delete after update does delete from the index + // ALF-8007 + // Already seen the delete in the TX and it is skipped (should only skip deletes in the same flush) + + nodeService.deleteNode(one); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(0, results.length()); + results.close(); + + // Check unreported ... create, query, update, delete + // ... create, query, move, delete + + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007-2\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(0, results.length()); + results.close(); + + properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, "ALF-8007-2"); + NodeRef two = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-2"), ContentModel.TYPE_CONTENT, properties).getChildRef(); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007-2\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(1, results.length()); + results.close(); + + desc1 = new MLText(); + desc1.addValue(Locale.ENGLISH, "ALF 8007 2"); + desc1.addValue(Locale.US, "ALF 8007 2"); + + nodeService.setProperty(two, ContentModel.PROP_DESCRIPTION, desc1); + nodeService.deleteNode(two); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007-2\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(0, results.length()); + results.close(); + + // ... create, query, move, delete + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007-3\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(0, results.length()); + results.close(); + + properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, "ALF-8007-3"); + NodeRef three = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-3"), ContentModel.TYPE_CONTENT, properties).getChildRef(); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007-3\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(1, results.length()); + results.close(); + + desc1 = new MLText(); + desc1.addValue(Locale.ENGLISH, "ALF 8007 3"); + desc1.addValue(Locale.US, "ALF 8007 3"); + + nodeService.moveNode(three, n1, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-3")); + nodeService.deleteNode(three); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007-3\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(0, results.length()); + results.close(); + + // ... create, move, query, delete + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007-4\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(0, results.length()); + results.close(); + + properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, "ALF-8007-4"); + NodeRef four = nodeService.createNode(rootNodeRef, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-4"), ContentModel.TYPE_CONTENT, properties).getChildRef(); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007-4\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(1, results.length()); + results.close(); + + desc1 = new MLText(); + desc1.addValue(Locale.ENGLISH, "ALF 8007 4"); + desc1.addValue(Locale.US, "ALF 8007 4"); + + nodeService.moveNode(four, n1, ASSOC_TYPE_QNAME, QName.createQName("{namespace}ALF-8007-4")); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007-4\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(1, results.length()); + results.close(); + + nodeService.deleteNode(four); + + sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + sp.setQuery("cm:name:\"ALF-8007-4\""); + sp.addStore(rootNodeRef.getStoreRef()); + sp.excludeDataInTheCurrentTransaction(false); + + results = serviceRegistry.getSearchService().query(sp); + assertEquals(0, results.length()); + results.close(); + + } + public void testPublicServiceSearchServicePaging() throws Exception { @@ -4763,6 +4980,75 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener // Test wildcards in text + //ALF-2389 + //results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "text-indexed-stored-tokenised-atomic"))+":*en*", null); + //assertEquals(0, results.length()); + //results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "text-indexed-stored-tokenised-atomic"))+":*a*", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "text-indexed-stored-tokenised-atomic"))+":*A*", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "text-indexed-stored-tokenised-atomic"))+":\"*a*\"", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "text-indexed-stored-tokenised-atomic"))+":\"*A\"*", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "text-indexed-stored-tokenised-atomic"))+":*s*", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "text-indexed-stored-tokenised-atomic"))+":*S*", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "text-indexed-stored-tokenised-atomic"))+":\"*s*\"", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "\\@" + escapeQName(QName.createQName(TEST_NAMESPACE, "text-indexed-stored-tokenised-atomic"))+":\"*S\"*", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TEXT:*A*", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TEXT:\"*a*\"", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TEXT:\"*A*\"", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TEXT:*a*", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TEXT:*Z*", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TEXT:*z*", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TEXT:\"*Z*\"", null); + assertEquals(1, results.length()); + results.close(); + + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TEXT:\"*z*\"", null); + assertEquals(1, results.length()); + results.close(); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "TEXT:laz*", null); assertEquals(1, results.length()); results.close(); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java index b8ce34e193..b99c2f0945 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; @@ -182,6 +183,7 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase reader.deleteDocument(doc); } } + td.close(); } catch (IOException e) { @@ -216,6 +218,7 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase reader.deleteDocument(doc); } } + td.close(); } catch (IOException e) { @@ -253,6 +256,7 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase reader.deleteDocument(doc); } } + td.close(); } } catch (IOException e) @@ -270,6 +274,11 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase * Consider if this information needs to be persisted for recovery */ protected Set deletions = new LinkedHashSet(); + + /** + * A list of deletions associated with the changes to nodes in the current flush + */ + protected Set deletionsSinceFlush = new HashSet(); /** * List of pending indexing commands. @@ -616,6 +625,7 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase getDeltaReader(); // outputTime("Delete "+nodeRef+" size = "+getDeltaWriter().docCount()); Set refs = new LinkedHashSet(); + Set containerRefs = new LinkedHashSet(); Set temp = null; switch(mode) @@ -623,19 +633,25 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase case MOVE: temp = deleteContainerAndBelow(nodeRef, getDeltaReader(), true, cascade); closeDeltaReader(); - refs.addAll(temp); - deletions.addAll(temp); + containerRefs.addAll(temp); temp = deleteContainerAndBelow(nodeRef, mainReader, false, cascade); - refs.addAll(temp); - deletions.addAll(temp); + containerRefs.addAll(temp); - leafrefs.addAll(deletePrimary(deletions, getDeltaReader(), true)); + temp = deletePrimary(containerRefs, getDeltaReader(), true); + leafrefs.addAll(temp); closeDeltaReader(); // May not have to delete references - leafrefs.addAll(deleteReference(deletions, getDeltaReader(), true)); + temp = deleteReference(containerRefs, getDeltaReader(), true); + leafrefs.addAll(temp); closeDeltaReader(); + + refs.addAll(containerRefs); refs.addAll(leafrefs); - deletions.addAll(leafrefs); + deletions.addAll(refs); + // should not be included as a delete for optimisation in deletionsSinceFlush + // should be optimised out + // defensive against any issue with optimisation of events + // the node has only moved - it still requires a real delete // make sure leaves are also removed from the delta before reindexing @@ -651,13 +667,21 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase closeDeltaReader(); refs.addAll(temp); deletions.addAll(temp); + // should not be included as a delete for optimisation in deletionsSinceFlush + // should be optimised out + // defensive against any issue with optimisation of events + // the nodes have not been deleted and would require a real delete temp = deleteContainerAndBelow(nodeRef, mainReader, false, cascade); refs.addAll(temp); deletions.addAll(temp); + // should not be included as a delete for optimisation + // should be optimised out + // defensive agaainst any issue with optimisation of events + // the nodes have not been deleted and would require a real delete break; case DELETE: // if already deleted don't do it again ... - if(deletions.contains(nodeRef)) + if(deletionsSinceFlush.contains(nodeRef)) { // nothing to do break; @@ -668,22 +692,30 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase // Most will skip any indexing as they will really have gone. temp = deleteContainerAndBelow(nodeRef, getDeltaReader(), true, cascade); closeDeltaReader(); - deletions.addAll(temp); + containerRefs.addAll(temp); refs.addAll(temp); temp = deleteContainerAndBelow(nodeRef, mainReader, false, cascade); - deletions.addAll(temp); - refs.addAll(temp); + containerRefs.addAll(temp); - leafrefs.clear(); - leafrefs.addAll(deletePrimary(deletions, getDeltaReader(), true)); + temp = deletePrimary(containerRefs, getDeltaReader(), true); + leafrefs.addAll(temp); closeDeltaReader(); - leafrefs.addAll(deletePrimary(deletions, mainReader, false)); + temp = deletePrimary(containerRefs, mainReader, false); + leafrefs.addAll(temp); + // May not have to delete references - leafrefs.addAll(deleteReference(deletions, getDeltaReader(), true)); + temp = deleteReference(containerRefs, getDeltaReader(), true); + leafrefs.addAll(temp); closeDeltaReader(); - leafrefs.addAll(deleteReference(deletions, mainReader, false)); + temp = deleteReference(containerRefs, mainReader, false); + leafrefs.addAll(temp); + + refs.addAll(containerRefs); refs.addAll(leafrefs); - deletions.addAll(leafrefs); + deletions.addAll(refs); + // do not delete anything we have deleted before in this flush + // probably OK to cache for the TX as a whole but done per flush => See ALF-8007 + deletionsSinceFlush.addAll(refs); // make sure leaves are also removed from the delta before reindexing @@ -835,6 +867,8 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase */ public void flushPending() throws LuceneIndexException { + // Make sure the in flush deletion list is clear at the start + deletionsSinceFlush.clear(); IndexReader mainReader = null; try { @@ -892,6 +926,7 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase commandList.clear(); indexImpl(forIndex, false); docs = getDeltaWriter().docCount(); + deletionsSinceFlush.clear(); } catch (IOException e) { @@ -961,7 +996,7 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase } /** - * Delete all index entries which do not start with the goven prefix + * Delete all index entries which do not start with the given prefix * * @param prefix */ @@ -980,6 +1015,9 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase if ((prefix == null) || nonStartwWith(ids, prefix)) { deletions.add(ids[ids.length - 1]); + // should be included in the deletion cache if we move back to caching at the TX level and not the flush level + // Entries here will currently be ignored as the list is cleared at the start and end of a flush. + deletionsSinceFlush.add(ids[ids.length - 1]); } } } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java index 5c8ac1de4e..be75fc52a2 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java @@ -3016,7 +3016,7 @@ public class IndexInfo implements IndexMonitor return "Index cleaner"; } - ExitState runImpl() + void runImpl() { Iterator i = deletableReaders.iterator(); @@ -3079,7 +3079,6 @@ public class IndexInfo implements IndexMonitor s_logger.warn("Failed to delete file - invalid canonical file", ioe); } } - return ExitState.DONE; } ExitState recoverImpl() @@ -3169,7 +3168,7 @@ public class IndexInfo implements IndexMonitor } } - private synchronized void done() + synchronized void done() { switch (scheduledState) { @@ -3184,22 +3183,6 @@ public class IndexInfo implements IndexMonitor } } - private synchronized void reschedule() - { - switch (scheduledState) - { - case RECOVERY_SCHEDULED: - scheduledState = ScheduledState.SCHEDULED; - case SCHEDULED: - threadPoolExecutor.execute(this); - break; - case FAILED: - case UN_SCHEDULED: - default: - throw new IllegalStateException(); - } - } - private synchronized void rescheduleRecovery() { switch (scheduledState) @@ -3234,11 +3217,10 @@ public class IndexInfo implements IndexMonitor { try { - ExitState reschedule; switch (scheduledState) { case RECOVERY_SCHEDULED: - reschedule = recoverImpl(); + ExitState reschedule = recoverImpl(); s_logger.error(getLogName() + " has recovered - resuming ... "); if (reschedule == ExitState.RESCHEDULE) { @@ -3250,23 +3232,8 @@ public class IndexInfo implements IndexMonitor { s_logger.debug(getLogName() + " running ... "); } - reschedule = runImpl(); - if (reschedule == ExitState.RESCHEDULE) - { - if (s_logger.isDebugEnabled()) - { - s_logger.debug(getLogName() + " rescheduling ... "); - } - reschedule(); - } - else - { - if (s_logger.isDebugEnabled()) - { - s_logger.debug(getLogName() + " done "); - } - done(); - } + runImpl(); + done(); break; case FAILED: case UN_SCHEDULED: @@ -3297,7 +3264,7 @@ public class IndexInfo implements IndexMonitor } } - abstract ExitState runImpl() throws Exception; + abstract void runImpl() throws Exception; abstract ExitState recoverImpl() throws Exception; @@ -3311,12 +3278,57 @@ public class IndexInfo implements IndexMonitor return "Index merger"; } - ExitState runImpl() throws IOException + @Override + void done() + { + // Reschedule if we need to, based on the current index state, that may have changed since we last got the + // read lock + getReadLock(); + try + { + synchronized (this) + { + if (decideMergeAction() != MergeAction.NONE) + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug(getLogName() + " rescheduling ... "); + } + switch (scheduledState) + { + case RECOVERY_SCHEDULED: + scheduledState = ScheduledState.SCHEDULED; + case SCHEDULED: + threadPoolExecutor.execute(this); + break; + case FAILED: + case UN_SCHEDULED: + default: + throw new IllegalStateException(); + } + } + else + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug(getLogName() + " done "); + } + super.done(); + } + } + } + finally + { + releaseReadLock(); + } + } + + void runImpl() throws IOException { - // Get the read local to decide what to do + // Get the read lock to decide what to do // Single JVM to start with - MergeAction action = MergeAction.NONE; + MergeAction action; getReadLock(); try @@ -3348,97 +3360,94 @@ public class IndexInfo implements IndexMonitor } } - int indexes = 0; - boolean mergingIndexes = false; - int deltas = 0; - boolean applyingDeletions = false; - - for (IndexEntry entry : indexEntries.values()) - { - if (entry.getType() == IndexType.INDEX) - { - indexes++; - if ((entry.getStatus() == TransactionStatus.MERGE) || (entry.getStatus() == TransactionStatus.MERGE_TARGET)) - { - mergingIndexes = true; - } - - } - else if (entry.getType() == IndexType.DELTA) - { - if (entry.getStatus() == TransactionStatus.COMMITTED) - { - deltas++; - } - if (entry.getStatus() == TransactionStatus.COMMITTED_DELETING) - { - applyingDeletions = true; - deltas++; - } - } - } - - if (s_logger.isDebugEnabled()) - { - s_logger.debug("Indexes = " + indexes); - s_logger.debug("Merging = " + mergingIndexes); - s_logger.debug("Deltas = " + deltas); - s_logger.debug("Deleting = " + applyingDeletions); - } - - if (!mergingIndexes && !applyingDeletions) - { - - if (indexes > mergerTargetIndexes) - { - // Try merge - action = MergeAction.MERGE_INDEX; - } - else if (deltas > mergerTargetOverlays) - { - // Try delete - action = MergeAction.APPLY_DELTA_DELETION; - } - } + action = decideMergeAction(); } catch (IOException e) { s_logger.error("Error reading index file", e); - return ExitState.DONE; + return; } finally { releaseReadLock(); } + if (s_logger.isDebugEnabled()) + { + s_logger.debug(getLogName() + " Merger applying MergeAction." + action.toString()); + } if (action == MergeAction.APPLY_DELTA_DELETION) { - action = mergeDeletions(); + mergeDeletions(); } else if (action == MergeAction.MERGE_INDEX) { - action = mergeIndexes(); + mergeIndexes(); + } + if (s_logger.isDebugEnabled()) + { + dumpInfo(); + } + } + + /** + * @param action + */ + private MergeAction decideMergeAction() + { + MergeAction action = MergeAction.NONE; + int indexes = 0; + boolean mergingIndexes = false; + int deltas = 0; + boolean applyingDeletions = false; + + for (IndexEntry entry : indexEntries.values()) + { + if (entry.getType() == IndexType.INDEX) + { + indexes++; + if ((entry.getStatus() == TransactionStatus.MERGE) || (entry.getStatus() == TransactionStatus.MERGE_TARGET)) + { + mergingIndexes = true; + } + } + else if (entry.getType() == IndexType.DELTA) + { + if (entry.getStatus() == TransactionStatus.COMMITTED) + { + deltas++; + } + if (entry.getStatus() == TransactionStatus.COMMITTED_DELETING) + { + applyingDeletions = true; + deltas++; + } + } } - if (action == MergeAction.NONE) + if (s_logger.isDebugEnabled()) { - if (s_logger.isDebugEnabled()) - { - s_logger.debug(getLogName() + " Merger exiting with MergeAction.NONE. DONE. "); - dumpInfo(); - } - return ExitState.DONE; + s_logger.debug("Indexes = " + indexes); + s_logger.debug("Merging = " + mergingIndexes); + s_logger.debug("Deltas = " + deltas); + s_logger.debug("Deleting = " + applyingDeletions); } - else + + if (!mergingIndexes && !applyingDeletions) { - if (s_logger.isDebugEnabled()) + if (indexes > mergerTargetIndexes) { - s_logger.debug(getLogName() + " Merger exiting with MergeAction." + action.toString() + ". RESCHEDULE. "); - dumpInfo(); - } - return ExitState.RESCHEDULE; + // Try merge + action = MergeAction.MERGE_INDEX; + } + else if (deltas > mergerTargetOverlays) + { + // Try delete + action = MergeAction.APPLY_DELTA_DELETION; + } } + return action; } ExitState recoverImpl() @@ -3547,7 +3556,7 @@ public class IndexInfo implements IndexMonitor return ExitState.DONE; } - MergeAction mergeDeletions() throws IOException + void mergeDeletions() throws IOException { if (s_logger.isDebugEnabled()) { @@ -3641,7 +3650,7 @@ public class IndexInfo implements IndexMonitor if (toDelete.size() == 0) { - return MergeAction.NONE; + return; } // Build readers @@ -3855,10 +3864,9 @@ public class IndexInfo implements IndexMonitor { releaseWriteLock(); } - return MergeAction.APPLY_DELTA_DELETION; } - MergeAction mergeIndexes() throws IOException + void mergeIndexes() throws IOException { if (s_logger.isDebugEnabled()) @@ -3960,7 +3968,7 @@ public class IndexInfo implements IndexMonitor if (toMerge.size() == 0) { - return MergeAction.NONE; + return; } String mergeTargetId = null; @@ -4130,7 +4138,6 @@ public class IndexInfo implements IndexMonitor s_logger.debug("..done merging"); } - return MergeAction.MERGE_INDEX; } private final int findMergeIndex(long min, long max, int target, List entries) throws IOException diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationBootstrapTest.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationBootstrapTest.java index 4946d7a1cd..f6cf2fab07 100644 --- a/source/java/org/alfresco/repo/security/authentication/AuthenticationBootstrapTest.java +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationBootstrapTest.java @@ -35,7 +35,7 @@ public class AuthenticationBootstrapTest extends TestCase * Creates the application context in the context of the test (not statically) and checks * that no residual authentication is left hanging around. */ - public void testBoostrap() + public void testBootstrap() { // Start the context ApplicationContextHelper.getApplicationContext(); diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java index 0f793c0c30..b309d14116 100644 --- a/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationTest.java @@ -40,6 +40,7 @@ import net.sf.acegisecurity.LockedException; import net.sf.acegisecurity.UserDetails; import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.management.subsystems.ChildApplicationContextManager; @@ -49,6 +50,8 @@ import org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl.Exp import org.alfresco.repo.security.authentication.InMemoryTicketComponentImpl.Ticket; import org.alfresco.repo.security.person.UserNameMatcher; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; @@ -116,6 +119,8 @@ public class AuthenticationTest extends TestCase private PolicyComponent policyComponent; + private SimpleCache authenticationCache; + public AuthenticationTest() { super(); @@ -128,6 +133,13 @@ public class AuthenticationTest extends TestCase public void setUp() throws Exception { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + dialect = (Dialect) ctx.getBean("dialect"); nodeService = (NodeService) ctx.getBean("nodeService"); @@ -142,6 +154,7 @@ public class AuthenticationTest extends TestCase personService = (PersonService) ctx.getBean("personService"); userNameMatcher = (UserNameMatcher) ctx.getBean("userNameMatcher"); policyComponent = (PolicyComponent) ctx.getBean("policyComponent"); + authenticationCache = (SimpleCache) ctx.getBean("authenticationCache"); // permissionServiceSPI = (PermissionServiceSPI) // ctx.getBean("permissionService"); ticketsCache = (SimpleCache) ctx.getBean("ticketsCache"); @@ -170,7 +183,9 @@ public class AuthenticationTest extends TestCase Map props = createPersonProperties("Andy"); personAndyNodeRef = nodeService.createNode(typesNodeRef, children, ContentModel.TYPE_PERSON, container, props).getChildRef(); assertNotNull(personAndyNodeRef); - + + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); + deleteAndy(); authenticationComponent.clearCurrentSecurityContext(); } @@ -184,6 +199,7 @@ public class AuthenticationTest extends TestCase dao.setPasswordEncoder(passwordEncoder); dao.setUserNameMatcher(userNameMatcher); dao.setPolicyComponent(policyComponent); + dao.setAuthenticationCache(authenticationCache); if (dao.getUserOrNull("andy") != null) { @@ -400,6 +416,7 @@ public class AuthenticationTest extends TestCase dao.setPasswordEncoder(passwordEncoder); dao.setUserNameMatcher(userNameMatcher); dao.setPolicyComponent(policyComponent); + dao.setAuthenticationCache(authenticationCache); dao.createUser("Andy", "cabbage".toCharArray()); assertNotNull(dao.getUserOrNull("Andy")); diff --git a/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationServiceTest.java b/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationServiceTest.java index 4ba9729f54..8c0699d26e 100644 --- a/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationServiceTest.java +++ b/source/java/org/alfresco/repo/security/authentication/ChainingAuthenticationServiceTest.java @@ -24,6 +24,9 @@ import java.util.HashSet; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.cmr.security.AuthenticationService; public class ChainingAuthenticationServiceTest extends TestCase @@ -65,7 +68,12 @@ public class ChainingAuthenticationServiceTest extends TestCase @Override protected void setUp() throws Exception { - super.setUp(); + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } AuthenticationUtil authUtil = new AuthenticationUtil(); authUtil.setDefaultAdminUserName("admin"); diff --git a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java index 22c2d560eb..8dbd9f4b8c 100644 --- a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java @@ -34,6 +34,8 @@ import net.sf.acegisecurity.providers.encoding.PasswordEncoder; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; @@ -51,7 +53,7 @@ import org.alfresco.util.EqualsHelper; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataAccessException; -public class RepositoryAuthenticationDao implements MutableAuthenticationDao, InitializingBean +public class RepositoryAuthenticationDao implements MutableAuthenticationDao, InitializingBean, OnUpdatePropertiesPolicy, BeforeDeleteNodePolicy { private static final StoreRef STOREREF_USERS = new StoreRef("user", "alfrescoUserStore"); @@ -70,6 +72,8 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In /** User folder ref cache (Tennant aware) */ private Map userFolderRefs = new ConcurrentHashMap(4); + private SimpleCache authenticationCache; + public RepositoryAuthenticationDao() { super(); @@ -110,6 +114,11 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In this.policyComponent = policyComponent; } + public void setAuthenticationCache(SimpleCache authenticationCache) + { + this.authenticationCache = authenticationCache; + } + /* (non-Javadoc) * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ @@ -119,6 +128,10 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In OnUpdatePropertiesPolicy.QNAME, ContentModel.TYPE_PERSON, new JavaBehaviour(this, "onUpdateProperties")); + this.policyComponent.bindClassBehaviour( + BeforeDeleteNodePolicy.QNAME, + ContentModel.TYPE_USER, + new JavaBehaviour(this, "beforeDeleteNode")); } public UserDetails loadUserByUsername(String incomingUserName) throws UsernameNotFoundException, DataAccessException @@ -148,10 +161,19 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In { return null; } - - List results = nodeService.getChildAssocs(getUserFolderLocation(searchUserName), - ContentModel.ASSOC_CHILDREN, QName.createQName(ContentModel.USER_MODEL_URI, searchUserName)); - return results.isEmpty() ? null : results.get(0).getChildRef(); + + NodeRef result = authenticationCache.get(searchUserName); + if (result == null) + { + List results = nodeService.getChildAssocs(getUserFolderLocation(searchUserName), + ContentModel.ASSOC_CHILDREN, QName.createQName(ContentModel.USER_MODEL_URI, searchUserName)); + if (!results.isEmpty()) + { + result = tenantService.getName(results.get(0).getChildRef()); + authenticationCache.put(searchUserName, result); + } + } + return result; } public void createUser(String caseSensitiveUserName, char[] rawPassword) throws AuthenticationException @@ -529,7 +551,17 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In nodeService.setProperty(userNode, ContentModel.PROP_USER_USERNAME, uidAfter); nodeService.moveNode(userNode, nodeService.getPrimaryParent(userNode).getParentRef(), ContentModel.ASSOC_CHILDREN, QName.createQName(ContentModel.USER_MODEL_URI, uidAfter)); + authenticationCache.remove(uidBefore); } } } + + public void beforeDeleteNode(NodeRef nodeRef) + { + String userName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_USER_USERNAME); + if (userName != null) + { + authenticationCache.remove(userName); + } + } } diff --git a/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationService.java b/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationService.java index 7ef0f2c74b..31b3c76d20 100644 --- a/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationService.java +++ b/source/java/org/alfresco/repo/security/authentication/subsystems/SubsystemChainingAuthenticationService.java @@ -18,8 +18,12 @@ */ package org.alfresco.repo.security.authentication.subsystems; +import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.management.subsystems.ChildApplicationContextManager; @@ -43,6 +47,11 @@ public class SubsystemChainingAuthenticationService extends AbstractChainingAuth /** The source bean name. */ private String sourceBeanName; + + private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private Collection instanceIds; + private Map contexts = new TreeMap(); + private Map sourceBeans = new TreeMap(); /** * Sets the application context manager. @@ -66,6 +75,58 @@ public class SubsystemChainingAuthenticationService extends AbstractChainingAuth this.sourceBeanName = sourceBeanName; } + // Bring our cached copies of the source beans in line with the application context manager, using a RW lock to + // ensure consistency + private void refreshBeans() + { + boolean haveWriteLock = false; + try + { + if (this.instanceIds == null || !this.instanceIds.equals(this.applicationContextManager.getInstanceIds())) + { + this.lock.readLock().unlock(); + this.lock.writeLock().lock(); + haveWriteLock = true; + this.instanceIds = this.applicationContextManager.getInstanceIds(); + this.contexts.keySet().retainAll(this.instanceIds); + this.sourceBeans.keySet().retainAll(this.instanceIds); + } + + for (String instance : this.instanceIds) + { + ApplicationContext newContext = this.applicationContextManager.getApplicationContext(instance); + ApplicationContext context = this.contexts.get(instance); + if (context != newContext) + { + if (!haveWriteLock) + { + this.lock.readLock().unlock(); + this.lock.writeLock().lock(); + haveWriteLock = true; + } + newContext = this.applicationContextManager.getApplicationContext(instance); + this.contexts.put(instance, newContext); + try + { + this.sourceBeans.put(instance, newContext.getBean(this.sourceBeanName)); + } + catch (NoSuchBeanDefinitionException e) + { + this.sourceBeans.remove(instance); + } + } + } + } + finally + { + if (haveWriteLock) + { + this.lock.readLock().lock(); + this.lock.writeLock().unlock(); + } + } + } + /* * (non-Javadoc) * @see @@ -74,12 +135,13 @@ public class SubsystemChainingAuthenticationService extends AbstractChainingAuth @Override public MutableAuthenticationService getMutableAuthenticationService() { - for (String instance : this.applicationContextManager.getInstanceIds()) + this.lock.readLock().lock(); + try { - ApplicationContext context = this.applicationContextManager.getApplicationContext(instance); - try + refreshBeans(); + for (String instance : this.instanceIds) { - AuthenticationService authenticationService = (AuthenticationService) context.getBean(sourceBeanName); + AuthenticationService authenticationService = (AuthenticationService) this.sourceBeans.get(instance); // Only add active authentication services. E.g. we might have an ldap context that is only used for // synchronizing if (authenticationService instanceof MutableAuthenticationService @@ -90,12 +152,12 @@ public class SubsystemChainingAuthenticationService extends AbstractChainingAuth return (MutableAuthenticationService) authenticationService; } } - catch (NoSuchBeanDefinitionException e) - { - // Ignore and continue - } + return null; + } + finally + { + this.lock.readLock().unlock(); } - return null; } /* @@ -107,12 +169,13 @@ public class SubsystemChainingAuthenticationService extends AbstractChainingAuth protected List getUsableAuthenticationServices() { List result = new LinkedList(); - for (String instance : this.applicationContextManager.getInstanceIds()) + this.lock.readLock().lock(); + try { - ApplicationContext context = this.applicationContextManager.getApplicationContext(instance); - try + refreshBeans(); + for (String instance : this.instanceIds) { - AuthenticationService authenticationService = (AuthenticationService) context.getBean(sourceBeanName); + AuthenticationService authenticationService = (AuthenticationService) this.sourceBeans.get(instance); // Only add active authentication components. E.g. we might have an ldap context that is only used for // synchronizing if (!(authenticationService instanceof ActivateableBean) @@ -122,12 +185,12 @@ public class SubsystemChainingAuthenticationService extends AbstractChainingAuth result.add(authenticationService); } } - catch (NoSuchBeanDefinitionException e) - { - // Ignore and continue - } + return result; + } + finally + { + this.lock.readLock().unlock(); } - return result; } } diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java index fe97f4a217..8f4c9443a7 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java @@ -389,6 +389,11 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean { return authorityDAO.getContainingAuthorities(type, name, immediate); } + + public NodeRef getAuthorityNodeRef(String name) + { + return authorityDAO.getAuthorityNodeRefOrNull(name); + } public void removeAuthority(String parentName, String childName) { diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java index d2451f421e..99dc0a2117 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java @@ -22,6 +22,7 @@ import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -30,12 +31,16 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.permissions.AclDAO; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.ServiceRegistry; +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.datatype.DefaultTypeConverter; @@ -45,6 +50,7 @@ import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.springframework.context.ApplicationContext; @@ -81,6 +87,13 @@ public class AuthorityServiceTest extends TestCase public void setUp() throws Exception { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); authenticationComponentImpl = (AuthenticationComponent) ctx.getBean("authenticationComponent"); authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService"); @@ -245,7 +258,7 @@ public class AuthorityServiceTest extends TestCase public void test_ETWOTWO_400() { - String auth = pubAuthorityService.createAuthority(AuthorityType.GROUP, "wo\"of"); + pubAuthorityService.createAuthority(AuthorityType.GROUP, "wo\"of"); Set authorities = pubAuthorityService.findAuthorities(AuthorityType.GROUP, null, true, "wo\"of*", AuthorityService.ZONE_APP_DEFAULT); assertEquals(1, authorities.size()); } @@ -350,7 +363,7 @@ public class AuthorityServiceTest extends TestCase assertTrue(authorityService.hasAdminAuthority()); assertTrue(pubAuthorityService.hasAdminAuthority()); Set authorities = authorityService.getAuthorities(); - assertEquals("Unexpected result: " + authorities, 4, authorityService.getAuthorities().size()); + assertEquals("Unexpected result: " + authorities, 6, authorityService.getAuthorities().size()); } public void testAuthorities() @@ -360,7 +373,7 @@ public class AuthorityServiceTest extends TestCase assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.EVERYONE).size()); assertTrue(pubAuthorityService.getAllAuthorities(AuthorityType.EVERYONE).contains(PermissionService.ALL_AUTHORITIES)); // groups added for email and admin - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertFalse(pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).contains(PermissionService.ALL_AUTHORITIES)); assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GUEST).size()); assertTrue(pubAuthorityService.getAllAuthorities(AuthorityType.GUEST).contains(PermissionService.GUEST_AUTHORITY)); @@ -439,14 +452,14 @@ public class AuthorityServiceTest extends TestCase { String auth; - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); - auth = pubAuthorityService.createAuthority(AuthorityType.GROUP, "woof"); - assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth = pubAuthorityService.createAuthority(AuthorityType.GROUP, "woof"); + assertEquals(8, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); pubAuthorityService.deleteAuthority(auth); - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); @@ -467,43 +480,43 @@ public class AuthorityServiceTest extends TestCase String auth5; assertFalse(pubAuthorityService.authorityExists(pubAuthorityService.getName(AuthorityType.GROUP, "one"))); - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one"); assertTrue(pubAuthorityService.authorityExists(auth1)); - assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); - auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two"); - assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(8, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two"); + assertEquals(9, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three"); pubAuthorityService.addAuthority(auth1, auth3); - assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(10, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four"); pubAuthorityService.addAuthority(auth1, auth4); - assertEquals(6, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(11, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five"); pubAuthorityService.addAuthority(auth2, auth5); - assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(12, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); pubAuthorityService.deleteAuthority(auth5); - assertEquals(6, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(11, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); pubAuthorityService.deleteAuthority(auth4); - assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(10, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); pubAuthorityService.deleteAuthority(auth3); - assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(9, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); pubAuthorityService.deleteAuthority(auth2); - assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(8, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); pubAuthorityService.deleteAuthority(auth1); - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); @@ -563,37 +576,37 @@ public class AuthorityServiceTest extends TestCase String auth4; String auth5; - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one"); assertEquals("GROUP_one", auth1); - assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(8, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two"); assertEquals("GROUP_two", auth2); - assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(9, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three"); pubAuthorityService.addAuthority(auth1, auth3); assertEquals("GROUP_three", auth3); - assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(10, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four"); pubAuthorityService.addAuthority(auth1, auth4); assertEquals("GROUP_four", auth4); - assertEquals(6, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(11, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five"); pubAuthorityService.addAuthority(auth2, auth5); assertEquals("GROUP_five", auth5); - assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(12, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); //System.out.println("Users: "+ pubAuthorityService.getAllAuthorities(AuthorityType.USER)); checkAuthorityCollectionSize(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER), AuthorityType.USER); pubAuthorityService.addAuthority(auth5, "andy"); - assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(12, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) checkAuthorityCollectionSize(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER), AuthorityType.USER); assertEquals(2, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); @@ -610,8 +623,8 @@ public class AuthorityServiceTest extends TestCase assertTrue(pubAuthorityService.getContainedAuthorities(null, auth5, false).contains("andy")); pubAuthorityService.removeAuthority(auth5, "andy"); - assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(12, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) checkAuthorityCollectionSize(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER), AuthorityType.USER); assertEquals(0, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); @@ -634,33 +647,33 @@ public class AuthorityServiceTest extends TestCase String auth4; String auth5; - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); - auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one"); - assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); - auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two"); - assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one"); + assertEquals(8, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two"); + assertEquals(9, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three"); pubAuthorityService.addAuthority(auth1, auth3); - assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(10, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four"); pubAuthorityService.addAuthority(auth1, auth4); - assertEquals(6, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(11, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five"); pubAuthorityService.addAuthority(auth2, auth5); - assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(12, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); pubAuthorityService.addAuthority(auth5, "andy"); pubAuthorityService.addAuthority(auth1, "andy"); - assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(12, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) checkAuthorityCollectionSize(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER), AuthorityType.USER); assertEquals(3, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); @@ -678,8 +691,8 @@ public class AuthorityServiceTest extends TestCase pubAuthorityService.removeAuthority(auth1, "andy"); - assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(12, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) checkAuthorityCollectionSize(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER), AuthorityType.USER); assertEquals(2, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); @@ -704,33 +717,33 @@ public class AuthorityServiceTest extends TestCase String auth4; String auth5; - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); - auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one"); - assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); - auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two"); - assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one"); + assertEquals(8, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two"); + assertEquals(9, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "three"); pubAuthorityService.addAuthority(auth1, auth3); - assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(10, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "four"); pubAuthorityService.addAuthority(auth1, auth4); - assertEquals(6, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(11, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "five"); pubAuthorityService.addAuthority(auth2, auth5); - assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(12, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); checkAuthorityCollectionSize(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER), AuthorityType.USER); pubAuthorityService.addAuthority(auth5, "andy"); pubAuthorityService.addAuthority(auth1, "andy"); - assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(12, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(5, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) checkAuthorityCollectionSize(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER), AuthorityType.USER); assertEquals(3, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); @@ -748,10 +761,10 @@ public class AuthorityServiceTest extends TestCase pubAuthorityService.addAuthority(auth3, auth2); - assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(12, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); // Number of root authorities has been reduced since auth2 is no longer an orphan - assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(4, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); // The next call looks for people not users :-) checkAuthorityCollectionSize(3, pubAuthorityService.getAllAuthorities(AuthorityType.USER), AuthorityType.USER); assertEquals(4, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); @@ -772,6 +785,65 @@ public class AuthorityServiceTest extends TestCase } + public void testGetAuthorityNodeRef() + { + String ADMIN_GROUP = "GROUP_ALFRESCO_ADMINISTRATORS"; + String NEW_GROUP = "GROUP_NEWLY_ADDED"; + + int rootCount = pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size(); + int allCount = pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size(); + + // Should have a root group "GROUP_ALFRESCO_ADMINISTRATORS" + Set root = pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP); + assertTrue( + ADMIN_GROUP + " not found in " + root, + root.contains(ADMIN_GROUP) + ); + NodeRef rootNode = pubAuthorityService.getAuthorityNodeRef(ADMIN_GROUP); + + // Check it makes sense + assertEquals( + ADMIN_GROUP, + nodeService.getProperty(rootNode, ContentModel.PROP_AUTHORITY_NAME) + ); + + + // Now add a child + pubAuthorityService.createAuthority(AuthorityType.GROUP, NEW_GROUP.replace("GROUP_", "")); + pubAuthorityService.setAuthorityDisplayName(NEW_GROUP, NEW_GROUP); + pubAuthorityService.addAuthority(ADMIN_GROUP, NEW_GROUP); + assertEquals(rootCount, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(allCount+1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + + // Check that + NodeRef newNode = pubAuthorityService.getAuthorityNodeRef(NEW_GROUP); + assertEquals( + NEW_GROUP, + nodeService.getProperty(newNode, ContentModel.PROP_AUTHORITY_NAME) + ); + + // Should be siblings + assertEquals( + nodeService.getPrimaryParent(rootNode).getParentRef(), + nodeService.getPrimaryParent(newNode).getParentRef() + ); + + // With an association between them + List members = nodeService.getChildAssocs( + rootNode, ContentModel.ASSOC_MEMBER, + RegexQNamePattern.MATCH_ALL + ); + boolean found = false; + for(ChildAssociationRef assoc : members) + { + if(assoc.getChildRef().equals(newNode)) found = true; + } + assertTrue( + "Association from child to parent group not found", + found + ); + } + public void test_AR_1510() { personService.getPerson("andy1"); @@ -780,8 +852,8 @@ public class AuthorityServiceTest extends TestCase personService.getPerson("andy4"); personService.getPerson("andy5"); personService.getPerson("andy6"); - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); String auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one"); pubAuthorityService.addAuthority(auth1, "andy1"); String auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two"); @@ -958,8 +1030,8 @@ public class AuthorityServiceTest extends TestCase personService.getPerson("an3dy"); assertTrue(personService.personExists("an3dy")); - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); String auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "one"); pubAuthorityService.addAuthority(auth1, "1234"); String auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "two"); @@ -1003,8 +1075,8 @@ public class AuthorityServiceTest extends TestCase public void testGroupNameTokenisation() { - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); String auth1234 = pubAuthorityService.createAuthority(AuthorityType.GROUP, "1234"); assertEquals(0, pubAuthorityService.getContainedAuthorities(AuthorityType.GROUP, auth1234, false).size()); @@ -1037,8 +1109,8 @@ public class AuthorityServiceTest extends TestCase pubAuthorityService.deleteAuthority(authC1); pubAuthorityService.deleteAuthority(auth1234); - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); - assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(3, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); } public void testAdminGroup() diff --git a/source/java/org/alfresco/repo/security/authority/DuplicateAuthorityTest.java b/source/java/org/alfresco/repo/security/authority/DuplicateAuthorityTest.java index 477b85b108..7949d368c3 100644 --- a/source/java/org/alfresco/repo/security/authority/DuplicateAuthorityTest.java +++ b/source/java/org/alfresco/repo/security/authority/DuplicateAuthorityTest.java @@ -26,9 +26,12 @@ import java.util.concurrent.TimeUnit; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileExistsException; @@ -79,6 +82,13 @@ public class DuplicateAuthorityTest extends TestCase @Override public void setUp() throws Exception { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry"); transactionService = serviceRegistry.getTransactionService(); retryingTransactionHelper = transactionService.getRetryingTransactionHelper(); diff --git a/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java b/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java index 5f1509497a..e0d13fc1ce 100644 --- a/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java +++ b/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java @@ -28,7 +28,9 @@ public class ExtendedPermissionServiceTest extends AbstractPermissionTest { public void testGroupPermission() { + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); personService.getPerson("andy"); + authenticationComponent.clearCurrentSecurityContext(); runAs("andy"); assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); @@ -45,7 +47,9 @@ public class ExtendedPermissionServiceTest extends AbstractPermissionTest public void testDeletePermissionByRecipient() { + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); personService.getPerson("andy"); + authenticationComponent.clearCurrentSecurityContext(); runAs("andy"); assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); diff --git a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java index dce15a44e1..167a013777 100644 --- a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java +++ b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java @@ -342,6 +342,11 @@ public class SimpleAuthorityServiceImpl implements AuthorityService return null; } + public NodeRef getAuthorityNodeRef(String name) + { + return null; + } + public Set findAuthorities(AuthorityType type, String parentAuthority, boolean immediate, String displayNamePattern, String zoneName) { diff --git a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceTest.java b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceTest.java index a9e8c97513..140f459eef 100644 --- a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceTest.java +++ b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceTest.java @@ -23,9 +23,12 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; @@ -62,6 +65,13 @@ public class SimpleAuthorityServiceTest extends TestCase public void setUp() throws Exception { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService"); authorityService = (AuthorityService) ctx.getBean("authorityService"); @@ -118,7 +128,7 @@ public class SimpleAuthorityServiceTest extends TestCase authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); assertTrue(authorityService.hasAdminAuthority()); assertTrue(pubAuthorityService.hasAdminAuthority()); - assertEquals(4, authorityService.getAuthorities().size()); + assertEquals(6, authorityService.getAuthorities().size()); } public void testAuthorities() @@ -130,7 +140,7 @@ public class SimpleAuthorityServiceTest extends TestCase assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.EVERYONE).size()); assertTrue(pubAuthorityService.getAllAuthorities(AuthorityType.EVERYONE).contains( PermissionService.ALL_AUTHORITIES)); - assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(7, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GUEST).size()); assertTrue(pubAuthorityService.getAllAuthorities(AuthorityType.GUEST).contains(PermissionService.GUEST_AUTHORITY)); assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.OWNER).size()); diff --git a/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java b/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java index 8c42fa97e6..4fa7bebea6 100644 --- a/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java +++ b/source/java/org/alfresco/repo/security/permissions/dynamic/LockOwnerDynamicAuthorityTest.java @@ -26,10 +26,13 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.lock.LockService; @@ -97,6 +100,13 @@ public class LockOwnerDynamicAuthorityTest extends TestCase public void setUp() throws Exception { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + nodeService = (NodeService) ctx.getBean("nodeService"); authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService"); authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); diff --git a/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java b/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java index 92c5c0d444..1d96068cda 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/AbstractPermissionTest.java @@ -22,11 +22,11 @@ import java.io.Serializable; import java.util.HashMap; import java.util.Map; -import javax.transaction.Status; import javax.transaction.UserTransaction; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.permissions.AclDAO; @@ -36,7 +36,9 @@ import org.alfresco.repo.security.authentication.MutableAuthenticationDao; import org.alfresco.repo.security.authority.AuthorityDAO; import org.alfresco.repo.security.permissions.PermissionReference; import org.alfresco.repo.security.permissions.PermissionServiceSPI; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeRef; @@ -107,6 +109,13 @@ public class AbstractPermissionTest extends TestCase public void setUp() throws Exception { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + nodeService = (NodeService) applicationContext.getBean("nodeService"); dictionaryService = (DictionaryService) applicationContext.getBean(ServiceRegistry.DICTIONARY_SERVICE .getLocalName()); @@ -174,11 +183,14 @@ public class AbstractPermissionTest extends TestCase @Override protected void tearDown() throws Exception { - - if ((testTX.getStatus() == Status.STATUS_ACTIVE) || (testTX.getStatus() == Status.STATUS_MARKED_ROLLBACK)) + try { testTX.rollback(); } + catch (Throwable e) + { + e.printStackTrace(); + } AuthenticationUtil.clearCurrentSecurityContext(); super.tearDown(); } diff --git a/source/java/org/alfresco/repo/security/permissions/impl/AbstractReadPermissionTest.java b/source/java/org/alfresco/repo/security/permissions/impl/AbstractReadPermissionTest.java index 484932ff08..67ba95d6a8 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/AbstractReadPermissionTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/AbstractReadPermissionTest.java @@ -5,11 +5,11 @@ import java.util.HashMap; import java.util.Map; import java.util.Random; -import javax.transaction.Status; import javax.transaction.UserTransaction; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.content.MimetypeMap; @@ -23,6 +23,8 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; import org.alfresco.repo.security.authority.AuthorityDAO; import org.alfresco.repo.security.permissions.PermissionServiceSPI; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; @@ -563,8 +565,13 @@ public class AbstractReadPermissionTest extends TestCase public void setUp() throws Exception { - super.setUp(); - + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + nodeService = (NodeService) applicationContext.getBean("nodeService"); dictionaryService = (DictionaryService) applicationContext.getBean(ServiceRegistry.DICTIONARY_SERVICE .getLocalName()); @@ -598,10 +605,10 @@ public class AbstractReadPermissionTest extends TestCase StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.nanoTime()); rootNodeRef = nodeService.getRootNode(storeRef); - QName children = ContentModel.ASSOC_CHILDREN; - QName system = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "system"); - QName container = ContentModel.TYPE_CONTAINER; - QName types = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "people"); +// QName children = ContentModel.ASSOC_CHILDREN; +// QName system = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "system"); +// QName container = ContentModel.TYPE_CONTAINER; +// QName types = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "people"); // systemNodeRef = nodeService.createNode(rootNodeRef, children, system, container).getChildRef(); // NodeRef typesNodeRef = nodeService.createNode(systemNodeRef, children, types, container).getChildRef(); @@ -628,9 +635,7 @@ public class AbstractReadPermissionTest extends TestCase authenticationService.deleteAuthentication(AuthenticationUtil.getAdminUserName()); } authenticationService.createAuthentication(AuthenticationUtil.getAdminUserName(), "admin".toCharArray()); - - authenticationComponent.clearCurrentSecurityContext(); - + fService = (AVMService)applicationContext.getBean("AVMService"); fIndexerAndSearcher = (IndexerAndSearcher)applicationContext.getBean("indexerAndSearcherFactory"); wpService = (WebProjectService)applicationContext.getBean("WebProjectService"); @@ -662,23 +667,14 @@ public class AbstractReadPermissionTest extends TestCase protected void tearDown() throws Exception { -// if(fService.getStore(AVMStore) != null) -// { -// fService.purgeStore(AVMStore); -// } -// for(String authority : authorities) -// { -// deleteAuthentication(authority); -// } -// for(String authority : webAuthorities) -// { -// deleteAuthentication(authority); -// } - - if ((testTX.getStatus() == Status.STATUS_ACTIVE) || (testTX.getStatus() == Status.STATUS_MARKED_ROLLBACK)) - { + try + { testTX.rollback(); } + catch (Throwable e) + { + e.printStackTrace(); + } AuthenticationUtil.clearCurrentSecurityContext(); super.tearDown(); } diff --git a/source/java/org/alfresco/repo/security/permissions/impl/AclDaoComponentTest.java b/source/java/org/alfresco/repo/security/permissions/impl/AclDaoComponentTest.java index 46fc90ab92..f4caaf270b 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/AclDaoComponentTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/AclDaoComponentTest.java @@ -30,6 +30,7 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.permissions.AclDAO; import org.alfresco.repo.security.authentication.AuthenticationComponent; @@ -44,6 +45,8 @@ import org.alfresco.repo.security.permissions.PermissionReference; import org.alfresco.repo.security.permissions.PermissionServiceSPI; import org.alfresco.repo.security.permissions.SimpleAccessControlEntry; import org.alfresco.repo.security.permissions.SimpleAccessControlListProperties; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeRef; @@ -105,6 +108,13 @@ public class AclDaoComponentTest extends TestCase public void setUp() throws Exception { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + aclDaoComponent = (AclDAO) applicationContext.getBean("aclDAO"); nodeService = (NodeService) applicationContext.getBean("nodeService"); diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java index 862ec1cc31..ac1ea3d4a8 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceTest.java @@ -359,8 +359,8 @@ public class PermissionServiceTest extends AbstractPermissionTest public void testChangeGroupUid() { - personService.getPerson("andy"); runAs("admin"); + personService.getPerson("andy"); NodeRef one = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); authorityService.createAuthority(AuthorityType.GROUP, "ONE"); authorityService.addAuthority("GROUP_ONE", "andy"); @@ -2951,7 +2951,7 @@ public class PermissionServiceTest extends AbstractPermissionTest public void testAclInsertionPerformanceShared() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - for(int i = 0; i < 10000; i++) + for(int i = 0; i < 1000; i++) { nodeService.createNode(parent, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}child"+i), ContentModel.TYPE_FOLDER).getChildRef(); } @@ -2965,7 +2965,7 @@ public class PermissionServiceTest extends AbstractPermissionTest public void testAclInsertionPerformanceDefining() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - for(int i = 0; i < 10000; i++) + for(int i = 0; i < 1000; i++) { NodeRef created = nodeService.createNode(parent, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}child"+i), ContentModel.TYPE_FOLDER).getChildRef(); permissionService.setPermission(new SimplePermissionEntry(created, getPermission(PermissionService.CONSUMER), "bob", AccessStatus.ALLOWED)); @@ -2980,7 +2980,7 @@ public class PermissionServiceTest extends AbstractPermissionTest public void testAclInsertionPerformanceMixed() { NodeRef parent = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}one"), ContentModel.TYPE_FOLDER).getChildRef(); - for(int i = 0; i < 10000; i++) + for(int i = 0; i < 1000; i++) { NodeRef created = nodeService.createNode(parent, ContentModel.ASSOC_CHILDREN, QName.createQName("{namespace}child"+i), ContentModel.TYPE_FOLDER).getChildRef(); if(i % 2 == 0) diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index 725c39d879..7896fd5004 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -32,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.MailActionExecuter; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.domain.permissions.AclDAO; import org.alfresco.repo.node.NodeServicePolicies; @@ -42,19 +43,32 @@ import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.search.SearcherException; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.PermissionServiceSPI; +import org.alfresco.repo.tenant.TenantDomainMismatchException; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.repo.transaction.TransactionalResourceHelper; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.admin.RepoUsage.UsageType; +import org.alfresco.service.cmr.admin.RepoUsageStatus; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.invitation.InvitationException; +import org.alfresco.service.cmr.model.FileFolderService; 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.repository.TemplateService; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.ResultSetRow; @@ -72,73 +86,58 @@ import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.EqualsHelper; import org.alfresco.util.GUID; +import org.alfresco.util.ModelUtil; import org.alfresco.util.PropertyCheck; +import org.alfresco.util.UrlUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; public class PersonServiceImpl extends TransactionListenerAdapter implements PersonService, NodeServicePolicies.BeforeCreateNodePolicy, NodeServicePolicies.OnCreateNodePolicy, NodeServicePolicies.BeforeDeleteNodePolicy, NodeServicePolicies.OnUpdatePropertiesPolicy + { private static Log logger = LogFactory.getLog(PersonServiceImpl.class); private static final String DELETE = "DELETE"; - private static final String SPLIT = "SPLIT"; - private static final String LEAVE = "LEAVE"; - public static final String SYSTEM_FOLDER_SHORT_QNAME = "sys:system"; - public static final String PEOPLE_FOLDER_SHORT_QNAME = "sys:people"; + private static final String SYSTEM_USAGE_WARN_LIMIT_USERS_EXCEEDED_VERBOSE = "system.usage.err.limit_users_exceeded_verbose"; - // IOC + private static final String KEY_POST_TXN_DUPLICATES = "PersonServiceImpl.KEY_POST_TXN_DUPLICATES"; + private static final String KEY_ALLOW_UID_UPDATE = "PersonServiceImpl.KEY_ALLOW_UID_UPDATE"; + private static final String KEY_USERS_CREATED = "PersonServiceImpl.KEY_USERS_CREATED"; private StoreRef storeRef; - private TransactionService transactionService; - private NodeService nodeService; - private TenantService tenantService; - private SearchService searchService; - private AuthorityService authorityService; - private MutableAuthenticationService authenticationService; - private DictionaryService dictionaryService; - private PermissionServiceSPI permissionServiceSPI; - private NamespacePrefixResolver namespacePrefixResolver; - private HomeFolderManager homeFolderManager; - private PolicyComponent policyComponent; - private BehaviourFilter policyBehaviourFilter; - - private boolean createMissingPeople; - - private static Set mutableProperties; - - private String defaultHomeFolderProvider; - - private boolean processDuplicates = true; - - private String duplicateMode = LEAVE; - - private boolean lastIsBest = true; - - private boolean includeAutoCreated = false; - private AclDAO aclDao; - private PermissionsManager permissionsManager; + private RepoAdminService repoAdminService; + private ServiceRegistry serviceRegistry; + + private boolean createMissingPeople; + private static Set mutableProperties; + private String defaultHomeFolderProvider; + private boolean processDuplicates = true; + private String duplicateMode = LEAVE; + private boolean lastIsBest = true; + private boolean includeAutoCreated = false; /** a transactionally-safe cache to be injected */ private SimpleCache> personCache; @@ -150,7 +149,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per private JavaBehaviour beforeCreateNodeValidationBehaviour; private JavaBehaviour beforeDeleteNodeValidationBehaviour; - + static { Set props = new HashSet(); @@ -192,7 +191,8 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per PropertyCheck.mandatory(this, "personCache", personCache); PropertyCheck.mandatory(this, "aclDao", aclDao); PropertyCheck.mandatory(this, "homeFolderManager", homeFolderManager); - + PropertyCheck.mandatory(this, "repoAdminService", repoAdminService); + beforeCreateNodeValidationBehaviour = new JavaBehaviour(this, "beforeCreateNodeValidation"); this.policyComponent.bindClassBehaviour( BeforeCreateNodePolicy.QNAME, @@ -219,8 +219,91 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per OnUpdatePropertiesPolicy.QNAME, ContentModel.TYPE_PERSON, new JavaBehaviour(this, "onUpdateProperties")); + + this.policyComponent.bindClassBehaviour( + OnUpdatePropertiesPolicy.QNAME, + ContentModel.TYPE_USER, + new JavaBehaviour(this, "onUpdatePropertiesUser")); } + /** + * {@inheritDoc} + */ + public void setCreateMissingPeople(boolean createMissingPeople) + { + this.createMissingPeople = createMissingPeople; + } + + public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) + { + this.namespacePrefixResolver = namespacePrefixResolver; + } + + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + public void setAuthenticationService(MutableAuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI) + { + this.permissionServiceSPI = permissionServiceSPI; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setRepoAdminService(RepoAdminService repoAdminService) + { + this.repoAdminService = repoAdminService; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setPolicyBehaviourFilter(BehaviourFilter policyBehaviourFilter) + { + this.policyBehaviourFilter = policyBehaviourFilter; + } + + public void setStoreUrl(String storeUrl) + { + this.storeRef = new StoreRef(storeUrl); + } + public UserNameMatcher getUserNameMatcher() { return userNameMatcher; @@ -273,15 +356,32 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per /** * Set the username to person cache. - * - * @param personCache - * a transactionally safe cache */ public void setPersonCache(SimpleCache> personCache) { this.personCache = personCache; } + /** + * You can't inject the {@link FileFolderService} directly, + * otherwise spring gets all confused with cyclic dependencies. + * So, look it up from the Service Registry as required + */ + private FileFolderService getFileFolderService() + { + return serviceRegistry.getFileFolderService(); + } + + /** + * You can't inject the {@link ActionService} directly, + * otherwise spring gets all confused with cyclic dependencies. + * So, look it up from the Service Registry as required + */ + private ActionService getActionService() + { + return serviceRegistry.getActionService(); + } + /** * {@inheritDoc} */ @@ -375,16 +475,29 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per } List refs = new ArrayList(allRefs.size()); + Set nodesToRemoveFromCache = new HashSet(); for (NodeRef nodeRef : allRefs) { - Serializable value = nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME); - String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, value); - if (userNameMatcher.matches(searchUserName, realUserName)) + if (nodeService.exists(nodeRef)) { - refs.add(nodeRef); + Serializable value = nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME); + String realUserName = DefaultTypeConverter.INSTANCE.convert(String.class, value); + if (userNameMatcher.matches(searchUserName, realUserName)) + { + refs.add(nodeRef); + } + } + else + { + nodesToRemoveFromCache.add(nodeRef); } } - + + if (!nodesToRemoveFromCache.isEmpty()) + { + allRefs.removeAll(nodesToRemoveFromCache); + } + NodeRef returnRef = null; if (refs.size() > 1) { @@ -427,9 +540,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per } } - private static final String KEY_POST_TXN_DUPLICATES = "PersonServiceImpl.KEY_POST_TXN_DUPLICATES"; - private static final String KEY_ALLOW_UID_UPDATE = "PersonServiceImpl.KEY_ALLOW_UID_UPDATE"; - /** * Get the txn-bound usernames that need cleaning up */ @@ -465,6 +575,11 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { // Get the duplicates in a form that can be read by the transaction work anonymous instance final Set postTxnDuplicates = getPostTxnDuplicates(); + if (postTxnDuplicates.size() == 0) + { + // Nothing to do + return; + } RetryingTransactionCallback processDuplicateWork = new RetryingTransactionCallback() { @@ -476,10 +591,11 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per if (duplicateMode.equalsIgnoreCase(SPLIT)) { + logger.info("Splitting " + postTxnDuplicates.size() + " duplicate person objects."); // Allow UIDs to be updated in this transaction AlfrescoTransactionSupport.bindResource(KEY_ALLOW_UID_UPDATE, Boolean.TRUE); split(postTxnDuplicates); - logger.info("Split duplicate person objects"); + logger.info("Split " + postTxnDuplicates.size() + " duplicate person objects."); } else if (duplicateMode.equalsIgnoreCase(DELETE)) { @@ -518,8 +634,10 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { for (NodeRef nodeRef : toSplit) { - String userName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME)); + String userName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME); + String newUserName = userName + GUID.generate(); nodeService.setProperty(nodeRef, ContentModel.PROP_USERNAME, userName + GUID.generate()); + logger.info(" New person object: " + newUserName); } } @@ -722,6 +840,19 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per public NodeRef createPerson(Map properties, Set zones) { String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties.get(ContentModel.PROP_USERNAME)); + + /** + * Check restrictions on the number of users + */ + Long maxUsers = repoAdminService.getRestrictions().getUsers(); + if (maxUsers != null) + { + // Get the set of users created in this transaction + Set usersCreated = TransactionalResourceHelper.getSet(KEY_USERS_CREATED); + usersCreated.add(userName); + AlfrescoTransactionSupport.bindListener(this); + } + AuthorityType authorityType = AuthorityType.getAuthorityType(userName); if (authorityType != AuthorityType.USER) { @@ -769,6 +900,115 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per return personRef; } + /** + * {@inheritDoc} + */ + public void notifyPerson(final String userName, final String password) + { + // Get the details of our user, or fail trying + NodeRef noderef = getPerson(userName, false); + Map userProps = nodeService.getProperties(noderef); + + // Do they have an email set? We can't email them if not... + String email = null; + if (userProps.containsKey(ContentModel.PROP_EMAIL)) + { + email = (String)userProps.get(ContentModel.PROP_EMAIL); + } + + if (email == null || email.length() == 0) + { + if (logger.isInfoEnabled()) + { + logger.info("Not sending new user notification to " + userName + " as no email address found"); + } + + return; + } + + // We need a freemarker model, so turn the QNames into + // something a bit more freemarker friendly + Map model = buildEmailTemplateModel(userProps); + model.put("password", password); // Not stored on the person + + // Set the details of the person sending the email into the model + NodeRef creatorNR = getPerson(AuthenticationUtil.getFullyAuthenticatedUser()); + Map creatorProps = nodeService.getProperties(creatorNR); + Map creator = buildEmailTemplateModel(creatorProps); + model.put("creator", (Serializable)creator); + + // Set share information into the model + String productName = ModelUtil.getProductName(repoAdminService); + model.put(TemplateService.KEY_SHARE_URL, UrlUtil.getShareUrl(serviceRegistry.getSysAdminParams()) + "/"); + model.put(TemplateService.KEY_PRODUCT_NAME, productName); + + // Set the details for the action + Map actionParams = new HashMap(); + actionParams.put(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable)model); + actionParams.put(MailActionExecuter.PARAM_TO, email); + actionParams.put(MailActionExecuter.PARAM_FROM, creatorProps.get(ContentModel.PROP_EMAIL)); + actionParams.put(MailActionExecuter.PARAM_SUBJECT, + I18NUtil.getMessage("invitation.notification.person.email.subject", productName)); + + // Pick the appropriate localised template + actionParams.put(MailActionExecuter.PARAM_TEMPLATE, getNotifyEmailTemplateNodeRef()); + + // Ask for the email to be sent asynchronously + Action mailAction = getActionService().createAction(MailActionExecuter.NAME, actionParams); + getActionService().executeAction(mailAction, noderef, false, true); + } + + private NodeRef getNotifyEmailTemplateNodeRef() + { + StoreRef spacesStore = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + String query = " PATH:\"app:company_home/app:dictionary/app:email_templates/cm:invite/cm:new-user-email.html.ftl\""; + + SearchParameters searchParams = new SearchParameters(); + searchParams.addStore(spacesStore); + searchParams.setLanguage(SearchService.LANGUAGE_LUCENE); + searchParams.setQuery(query); + + ResultSet results = null; + try + { + results = searchService.query(searchParams); + List nodeRefs = results.getNodeRefs(); + if (nodeRefs.size() == 1) + { + // Now localise this + NodeRef base = nodeRefs.get(0); + NodeRef local = getFileFolderService().getLocalizedSibling(base); + return local; + } + else + { + throw new InvitationException("Cannot find the email template!"); + } + } + catch (SearcherException e) + { + throw new InvitationException("Cannot find the email template!", e); + } + finally + { + if (results != null) + { + results.close(); + } + } + } + + private Map buildEmailTemplateModel(Map props) + { + Map model = new HashMap((int)(props.size()*1.5)); + for (QName qname : props.keySet()) + { + model.put(qname.getLocalName(), props.get(qname)); + model.put(qname.getLocalName().toLowerCase(), props.get(qname)); + } + return model; + } + /** * {@inheritDoc} */ @@ -831,7 +1071,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per if (typeQName.equals(ContentModel.TYPE_PERSON)) { String userName = (String) this.nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); - deletePersonImpl(userName, personRef); + deletePersonImpl(userName, personRef); } else { @@ -881,6 +1121,17 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per beforeDeleteNodeValidationBehaviour.enable(); } } + + /* + * Kick off the transaction listener for create user. It has the side-effect of + * recalculating the number of users. + */ + Long maxUsers = repoAdminService.getRestrictions().getUsers(); + if (maxUsers != null) + { + AlfrescoTransactionSupport.bindListener(this); + } + } /** @@ -1057,76 +1308,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per this.personCache.remove(userName.toLowerCase()); } - // IOC Setters - - /** - * {@inheritDoc} - */ - public void setCreateMissingPeople(boolean createMissingPeople) - { - this.createMissingPeople = createMissingPeople; - } - - public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) - { - this.namespacePrefixResolver = namespacePrefixResolver; - } - - public void setAuthorityService(AuthorityService authorityService) - { - this.authorityService = authorityService; - } - - public void setAuthenticationService(MutableAuthenticationService authenticationService) - { - this.authenticationService = authenticationService; - } - - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI) - { - this.permissionServiceSPI = permissionServiceSPI; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - public void setPolicyBehaviourFilter(BehaviourFilter policyBehaviourFilter) - { - this.policyBehaviourFilter = policyBehaviourFilter; - } - - public void setStoreUrl(String storeUrl) - { - this.storeRef = new StoreRef(storeUrl); - } - /** * {@inheritDoc} */ @@ -1180,7 +1361,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per return 0; } } - } } @@ -1192,14 +1372,11 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per return userNameMatcher.getUserNamesAreCaseSensitive(); } - /* + /** * When a uid is changed we need to create an alias for the old uid so permissions are not broken. This can happen * when an already existing user is updated via LDAP e.g. migration to LDAP, or when a user is auto created and then * updated by LDAP This is probably less likely after 3.2 and sync on missing person See * https://issues.alfresco.com/jira/browse/ETWOTWO-389 (non-Javadoc) - * - * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, - * java.util.Map, java.util.Map) */ public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) { @@ -1247,4 +1424,140 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per } } } -} + + /** + * Track the {@link ContentModel#PROP_ENABLED enabled/disabled} flag on {@link ContentModel#TYPE_USER cm:user}. + */ + public void onUpdatePropertiesUser(NodeRef nodeRef, Map before, Map after) + { + String userName = (String) after.get(ContentModel.PROP_USER_USERNAME); + if (userName == null) + { + // Won't find user + return; + } + // Get the person + NodeRef personNodeRef = getPersonOrNull(userName); + if (personNodeRef == null) + { + // Don't attempt to maintain enabled/disabled flag + return; + } + + // Check the enabled/disabled flag + Boolean enabled = (Boolean) after.get(ContentModel.PROP_ENABLED); + if (enabled == null || enabled.booleanValue()) + { + nodeService.removeAspect(personNodeRef, ContentModel.ASPECT_PERSON_DISABLED); + } + else + { + nodeService.addAspect(personNodeRef, ContentModel.ASPECT_PERSON_DISABLED, null); + } + + // Do post-commit user counting, if required + Set usersCreated = TransactionalResourceHelper.getSet(KEY_USERS_CREATED); + usersCreated.add(userName); + AlfrescoTransactionSupport.bindListener(this); + } + + /** + * {@inheritDoc} + */ + public void beforeCommit(boolean readOnly) + { + // check whether max users has been exceeded + RunAsWork getMaxUsersWork = new RunAsWork() + { + @Override + public Long doWork() throws Exception + { + return repoAdminService.getRestrictions().getUsers(); + } + }; + Long maxUsers = AuthenticationUtil.runAs(getMaxUsersWork, AuthenticationUtil.getSystemUserName()); + if(maxUsers == null) + { + return; + } + + Long users = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Long doWork() throws Exception + { + repoAdminService.updateUsage(UsageType.USAGE_USERS); + if(logger.isDebugEnabled()) + { + logger.debug("Number of users is " + repoAdminService.getUsage().getUsers()); + } + return repoAdminService.getUsage().getUsers(); + } + } , AuthenticationUtil.getSystemUserName()); + + // Get the set of users created in this transaction + Set usersCreated = TransactionalResourceHelper.getSet(KEY_USERS_CREATED); + + // If we exceed the limit, generate decent message about which users were being created, etc. + if (users > maxUsers) + { + List usersMsg = new ArrayList(5); + int i = 0; + for (String userCreated : usersCreated) + { + i++; + if (i > 5) + { + usersMsg.add(" ... more"); + break; + } + else + { + usersMsg.add(userCreated); + } + } + if (logger.isDebugEnabled()) + { + logger.debug("Maximum number of users exceeded: " + usersCreated); + } + throw AlfrescoRuntimeException.create(SYSTEM_USAGE_WARN_LIMIT_USERS_EXCEEDED_VERBOSE, maxUsers, usersMsg); + } + + // Get the usages and log any warnings + RepoUsageStatus usageStatus = repoAdminService.getUsageStatus(); + usageStatus.logMessages(logger); + } + + /** + * Helper for when creating new users and people: + * Updates the supplied username with any required tenant + * details, and ensures that the tenant domains match. + * If Multi-Tenant is disabled, returns the same username. + */ + public static String updateUsernameForTenancy(String username, TenantService tenantService) + throws TenantDomainMismatchException + { + if(! tenantService.isEnabled()) + { + // Nothing to do if not using multi tenant + return username; + } + + String currentDomain = tenantService.getCurrentUserDomain(); + if (! currentDomain.equals(TenantService.DEFAULT_DOMAIN)) + { + if (! tenantService.isTenantUser(username)) + { + // force domain onto the end of the username + username = tenantService.getDomainUser(username, currentDomain); + logger.warn("Added domain to username: " + username); + } + else + { + // Check the user's domain matches the current domain + // Throws a TenantDomainMismatchException if they don't match + tenantService.checkDomainUser(username); + } + } + return username; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/person/PersonTest.java b/source/java/org/alfresco/repo/security/person/PersonTest.java index a2cccc47a3..224b523102 100644 --- a/source/java/org/alfresco/repo/security/person/PersonTest.java +++ b/source/java/org/alfresco/repo/security/person/PersonTest.java @@ -38,7 +38,10 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -62,34 +65,37 @@ public class PersonTest extends TestCase private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private TransactionService transactionService; - private PersonService personService; - private BehaviourFilter policyBehaviourFilter; - private NodeService nodeService; - private NodeRef rootNodeRef; - private PermissionService permissionService; - private AuthorityService authorityService; - + private MutableAuthenticationDao authenticationDAO; private UserTransaction testTX; public PersonTest() { super(); - // TODO Auto-generated constructor stub } public void setUp() throws Exception { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); + + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + throw new AlfrescoRuntimeException( + "A previous tests did not clean up transaction: " + + AlfrescoTransactionSupport.getTransactionId()); + } + transactionService = (TransactionService) ctx.getBean("transactionService"); personService = (PersonService) ctx.getBean("personService"); nodeService = (NodeService) ctx.getBean("nodeService"); permissionService = (PermissionService) ctx.getBean("permissionService"); authorityService = (AuthorityService) ctx.getBean("authorityService"); + authenticationDAO = (MutableAuthenticationDao) ctx.getBean("authenticationDao"); policyBehaviourFilter = (BehaviourFilter) ctx.getBean("policyBehaviourFilter"); testTX = transactionService.getUserTransaction(); @@ -1152,4 +1158,27 @@ public class PersonTest extends TestCase personService.deletePerson(TEST_PERSON); } + + public void testDisableEnablePerson() + { + String userName = GUID.generate(); + + authenticationDAO.createUser(userName, "abc".toCharArray()); + + Map properties = createDefaultProperties( + userName, + "firstName", + "lastName", + "email@orgId", + "orgId", + null); + NodeRef personNodeRef = personService.createPerson(properties); + assertFalse("Person should not be disabled.", nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PERSON_DISABLED)); + + authenticationDAO.setEnabled(userName, true); + assertFalse("Person should not be disabled.", nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PERSON_DISABLED)); + + authenticationDAO.setEnabled(userName, false); + assertTrue("Person should be disabled.", nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PERSON_DISABLED)); + } } diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java index cdfea5b956..b9b95c02ac 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java @@ -452,13 +452,26 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl if (lockToken != null) { // Cancel the lock refresher - lockRefresher.shutdown(); - try + // Because we may hit a perfect storm when trying to interrupt workers in their unsynchronized getTask() + // method we can't wait indefinitely and may have to retry the shutdown + int trys = 0; + do { - lockRefresher.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + lockRefresher.shutdown(); + try + { + lockRefresher + .awaitTermination(ChainingUserRegistrySynchronizer.LOCK_TTL, TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) + { + } } - catch (InterruptedException e) + while (!lockRefresher.isTerminated() && trys++ < 3); + if (!lockRefresher.isTerminated()) { + lockRefresher.shutdownNow(); + ChainingUserRegistrySynchronizer.logger.error("Failed to shut down lock refresher"); } final String token = lockToken; diff --git a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java index 04163d43e1..b8183d66e0 100644 --- a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java +++ b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java @@ -24,12 +24,14 @@ import org.alfresco.cmis.CMISDictionaryService; import org.alfresco.cmis.CMISQueryService; import org.alfresco.cmis.CMISServices; import org.alfresco.mbeans.VirtServerRegistry; +import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.forms.FormService; import org.alfresco.repo.imap.ImapService; import org.alfresco.repo.lock.JobLockService; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.avm.AVMService; @@ -582,4 +584,17 @@ public class ServiceDescriptorRegistry { return (PublicServiceAccessService)getService(PUBLIC_SERVICE_ACCESS_SERVICE); } + + @Override + public RepoAdminService getRepoAdminService() + { + return (RepoAdminService)getService(REPO_ADMIN_SERVICE); + } + + @Override + public SysAdminParams getSysAdminParams() + { + final String beanName = "sysAdminParams"; + return (SysAdminParams) beanFactory.getBean(beanName); + } } diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index 16ee129cf3..dff70398c5 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -112,9 +112,9 @@ public class SiteServiceImpl implements SiteService, SiteModel private static final String MSG_CAN_NOT_UPDATE = "site_service.can_not_update"; private static final String MSG_CAN_NOT_DELETE = "site_service.can_not_delete"; private static final String MSG_SITE_NO_EXIST = "site_service.site_no_exist"; - private static final String MSG_CAN_NOT_REMOVE_MSHIP = "site_service.can_not_reomve_memebership"; + private static final String MSG_CAN_NOT_REMOVE_MSHIP = "site_service.can_not_remove_membership"; private static final String MSG_DO_NOT_CHANGE_MGR = "site_service.do_not_change_manager"; - private static final String MSG_CAN_NOT_CHANGE_MSHIP="site_service.can_not_change_memebership"; + private static final String MSG_CAN_NOT_CHANGE_MSHIP="site_service.can_not_change_membership"; private static final String MSG_SITE_CONTAINER_NOT_FOLDER = "site_service.site_container_not_folder"; /* Services */ @@ -1081,7 +1081,9 @@ public class SiteServiceImpl implements SiteService, SiteModel String cacheKey = this.tenantAdminService.getCurrentUserDomain() + '_' + shortName; this.siteNodeRefs.remove(cacheKey); - // Delete the node + // Delete the site node, marking it as "not to be archived" on the way. + // The site node will be permanently deleted immediately. See ALF-7888 for info on why + this.nodeService.addAspect(siteNodeRef, ContentModel.ASPECT_TEMPORARY, null); this.nodeService.deleteNode(siteNodeRef); // Delete the associated groups @@ -1344,36 +1346,42 @@ public class SiteServiceImpl implements SiteService, SiteModel */ private List getPermissionGroups(String siteShortName, String authorityName) { - List result = new ArrayList(5); - Set roles = this.permissionService.getSettablePermissions(SiteModel.TYPE_SITE); - + List fullResult = new ArrayList(5); + Set roles = this.permissionService.getSettablePermissions(SiteModel.TYPE_SITE); + + // First use the authority's cached recursive group memberships to answer the question quickly + Set authorityGroups = this.authorityService.getContainingAuthorities(AuthorityType.GROUP, + authorityName, false); for (String role : roles) { String roleGroup = getSiteRoleGroup(siteShortName, role, true); - Set authorities = this.authorityService.getContainedAuthorities(AuthorityType.USER, roleGroup, true); - if (authorities.contains(authorityName) == true) + if (authorityGroups.contains(roleGroup)) + { + fullResult.add(roleGroup); + } + } + + // Unfortunately, due to direct membership taking precendence, we can't answer the question quickly if more than one role has been inherited + if (fullResult.size() <= 1) + { + return fullResult; + } + + // Check direct group memberships + List result = new ArrayList(5); + authorityGroups = this.authorityService.getContainingAuthorities(AuthorityType.GROUP, + authorityName, true); + for (String role : roles) + { + String roleGroup = getSiteRoleGroup(siteShortName, role, true); + if (authorityGroups.contains(roleGroup)) { result.add(roleGroup); } } - - // If there are user permissions then they take priority - if (result.size() > 0) - { - return result; - } - - // Now do a deep search through all users and groups - for (String role : roles) - { - String roleGroup = getSiteRoleGroup(siteShortName, role, true); - Set authorities = this.authorityService.getContainedAuthorities(null, roleGroup, false); - if (authorities.contains(authorityName) == true) - { - result.add(roleGroup); - } - } - return result; + + // If there are user permissions then they take priority + return result.size() > 0 ? result : fullResult; } /** diff --git a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java index 16318868ed..84c31d063d 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java @@ -367,17 +367,13 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest /** * Test listSite methods. - * - * @throws Exception */ public void testListSites() throws Exception { - /** - * Check for no pre-existing sites before we start the test - */ + // Count sites List sites = this.siteService.listSites(null, null); - assertNotNull("sites already exist prior to starting test", sites); - assertTrue("sites already exist prior to starting test", sites.isEmpty()); + assertNotNull(sites); + int siteCount = sites.size(); // Create some sites this.siteService.createSite(TEST_SITE_PRESET, "mySiteOne", TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC); @@ -389,7 +385,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest // Get all the sites sites = this.siteService.listSites(null, null); assertNotNull(sites); - assertEquals(5, sites.size()); + assertEquals(5 + siteCount, sites.size()); // Get sites by matching name sites = this.siteService.listSites("One", null); @@ -667,11 +663,11 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest public void testIsPublic() { - // Check for no pre-existing sites before we start the test proper + // Count sites List sites = this.siteService.listSites(null, null); - assertNotNull("sites already exist prior to starting test", sites); - assertTrue("sites already exist prior to starting test", sites.isEmpty()); - + assertNotNull(sites); + int siteCount = sites.size(); + // Create a couple of sites as user one this.siteService.createSite(TEST_SITE_PRESET, "isPublicTrue", TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC); this.siteService.createSite(TEST_SITE_PRESET, "isPublicFalse", TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PRIVATE); @@ -679,14 +675,14 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest // Get the sites as user one sites = this.siteService.listSites(null, null); assertNotNull(sites); - assertEquals(2, sites.size()); + assertEquals(2 + siteCount, sites.size()); // Now get the sites as user two this.authenticationComponent.setCurrentUser(USER_TWO); sites = this.siteService.listSites(null, null); assertNotNull(sites); - assertEquals(1, sites.size()); - checkSiteInfo(sites.get(0), TEST_SITE_PRESET, "isPublicTrue", TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC); + assertEquals(1 + siteCount, sites.size()); +// checkSiteInfo(sites.get(0), TEST_SITE_PRESET, "isPublicTrue", TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC); // Make user 2 a member of the site //TestWithUserUtils.authenticateUser(USER_ONE, "PWD", this.authenticationService, this.authenticationComponent); @@ -697,7 +693,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest this.authenticationComponent.setCurrentUser(USER_TWO); sites = this.siteService.listSites(null, null); assertNotNull(sites); - assertEquals(2, sites.size()); + assertEquals(2 + siteCount, sites.size()); } public void testMembership() diff --git a/source/java/org/alfresco/repo/site/script/test_siteService.js b/source/java/org/alfresco/repo/site/script/test_siteService.js index 63e074f869..96bd74b167 100644 --- a/source/java/org/alfresco/repo/site/script/test_siteService.js +++ b/source/java/org/alfresco/repo/site/script/test_siteService.js @@ -41,6 +41,8 @@ function testCRUD() function testListSites() { + var sitesStart = siteService.listSites(null, null); + // Create a couple of sites siteService.createSite("sitePreset", "siteShortName", "siteTitle", "siteDescription", siteService.PUBLIC_SITE); siteService.createSite("sitePreset", "siteShortName2", "siteTitle", "siteDescription", siteService.PUBLIC_SITE); @@ -50,8 +52,7 @@ function testListSites() // Check the list test.assertNotNull(sites); - // Note: the result is 3 since we created a site to place in the model - test.assertEquals(3, sites.length); + test.assertEquals(2 + sitesStart.length, sites.length); // TODO .. check the filters } diff --git a/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java b/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java index 5953682a6b..b339f0c3cc 100644 --- a/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java +++ b/source/java/org/alfresco/repo/tagging/TaggingServiceImplTest.java @@ -25,8 +25,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.transaction.UserTransaction; - import junit.framework.TestCase; import org.alfresco.model.ContentModel; @@ -144,32 +142,35 @@ public class TaggingServiceImplTest extends TestCase if (init == false) { - UserTransaction tx = this.transactionService.getNonPropagatingUserTransaction(); - tx.begin(); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + // Authenticate as the system user + authenticationComponent.setSystemUserAsCurrentUser(); + + // Create the store and get the root node + TaggingServiceImplTest.storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); + TaggingServiceImplTest.rootNode = nodeService.getRootNode(TaggingServiceImplTest.storeRef); + + // Create the required tagging category + NodeRef catContainer = nodeService.createNode(TaggingServiceImplTest.rootNode, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "categoryContainer"), ContentModel.TYPE_CONTAINER).getChildRef(); + NodeRef catRoot = nodeService.createNode( + catContainer, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "categoryRoot"), + ContentModel.TYPE_CATEGORYROOT).getChildRef(); + nodeService.createNode( + catRoot, + ContentModel.ASSOC_CATEGORIES, + ContentModel.ASPECT_TAGGABLE, + ContentModel.TYPE_CATEGORY).getChildRef(); + + init = true; + return null; + }}); - // Authenticate as the system user - authenticationComponent.setSystemUserAsCurrentUser(); - - // Create the store and get the root node - TaggingServiceImplTest.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); - TaggingServiceImplTest.rootNode = this.nodeService.getRootNode(TaggingServiceImplTest.storeRef); - - // Create the required tagging category - NodeRef catContainer = nodeService.createNode(TaggingServiceImplTest.rootNode, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "categoryContainer"), ContentModel.TYPE_CONTAINER).getChildRef(); - NodeRef catRoot = nodeService.createNode( - catContainer, - ContentModel.ASSOC_CHILDREN, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "categoryRoot"), - ContentModel.TYPE_CATEGORYROOT).getChildRef(); - nodeService.createNode( - catRoot, - ContentModel.ASSOC_CATEGORIES, - ContentModel.ASPECT_TAGGABLE, - ContentModel.TYPE_CATEGORY).getChildRef(); - - init = true; - - tx.commit(); } // We want to know when tagging actions have finished running @@ -196,517 +197,724 @@ public class TaggingServiceImplTest extends TestCase private void createTestDocumentsAndFolders() throws Exception { - UserTransaction tx = this.transactionService.getNonPropagatingUserTransaction(); - tx.begin(); - - // Authenticate as the system user - authenticationComponent.setSystemUserAsCurrentUser(); - - String guid = GUID.generate(); - - // Create a folder - Map folderProps = new HashMap(1); - folderProps.put(ContentModel.PROP_NAME, "testFolder" + guid); - folder = this.nodeService.createNode( - TaggingServiceImplTest.rootNode, - ContentModel.ASSOC_CHILDREN, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder" + guid), - ContentModel.TYPE_FOLDER, - folderProps).getChildRef(); - - // Create a node - Map docProps = new HashMap(1); - docProps.put(ContentModel.PROP_NAME, "testDocument" + guid + ".txt"); - document = this.nodeService.createNode( - folder, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testDocument" + guid + ".txt"), - ContentModel.TYPE_CONTENT, - docProps).getChildRef(); - - Map props = new HashMap(1); - props.put(ContentModel.PROP_NAME, "subFolder" + guid); - subFolder = this.nodeService.createNode( - folder, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "subFolder" + guid), - ContentModel.TYPE_FOLDER, - props).getChildRef(); - - props = new HashMap(1); - props.put(ContentModel.PROP_NAME, "subDocument" + guid + ".txt"); - subDocument = this.nodeService.createNode( - subFolder, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "subDocument" + guid + ".txt"), - ContentModel.TYPE_CONTENT, - props).getChildRef(); - - tx.commit(); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + + // Authenticate as the system user + authenticationComponent.setSystemUserAsCurrentUser(); + + String guid = GUID.generate(); + + // Create a folder + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "testFolder" + guid); + folder = nodeService.createNode( + TaggingServiceImplTest.rootNode, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder" + guid), + ContentModel.TYPE_FOLDER, + folderProps).getChildRef(); + + // Create a node + Map docProps = new HashMap(1); + docProps.put(ContentModel.PROP_NAME, "testDocument" + guid + ".txt"); + document = nodeService.createNode( + folder, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testDocument" + guid + ".txt"), + ContentModel.TYPE_CONTENT, + docProps).getChildRef(); + + Map props = new HashMap(1); + props.put(ContentModel.PROP_NAME, "subFolder" + guid); + subFolder = nodeService.createNode( + folder, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "subFolder" + guid), + ContentModel.TYPE_FOLDER, + props).getChildRef(); + + props = new HashMap(1); + props.put(ContentModel.PROP_NAME, "subDocument" + guid + ".txt"); + subDocument = nodeService.createNode( + subFolder, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "subDocument" + guid + ".txt"), + ContentModel.TYPE_CONTENT, + props).getChildRef(); + return null; + } + }); } + private void removeTestDocumentsAndFolders() throws Exception { - UserTransaction tx = this.transactionService.getNonPropagatingUserTransaction(); - tx.begin(); - - // Authenticate as the system user - authenticationComponent.setSystemUserAsCurrentUser(); - - // If anything is a tag scope, stop it being - NodeRef[] nodes = new NodeRef[] { subDocument, subFolder, document, folder }; - for(NodeRef nodeRef : nodes) - { - if(taggingService.isTagScope(nodeRef)) - { - taggingService.removeTagScope(nodeRef); - } - } - - // Remove the sample nodes - for(NodeRef nodeRef : nodes) - { - nodeService.deleteNode(nodeRef); - } - - // Tidy up the audit component, now all the nodes have gone - auditService.clearAudit( - TaggingServiceImpl.TAGGING_AUDIT_APPLICATION_NAME, - 0l, System.currentTimeMillis()+1 - ); - - // All done - tx.commit(); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback(){ + @Override + public Void execute() throws Throwable + { + // Authenticate as the system user + authenticationComponent.setSystemUserAsCurrentUser(); + + // If anything is a tag scope, stop it being + NodeRef[] nodes = new NodeRef[] { subDocument, subFolder, document, folder }; + for(NodeRef nodeRef : nodes) + { + if(taggingService.isTagScope(nodeRef)) + { + taggingService.removeTagScope(nodeRef); + } + } + + // Remove the sample nodes + for(NodeRef nodeRef : nodes) + { + nodeService.deleteNode(nodeRef); + } + + // Tidy up the audit component, now all the nodes have gone + auditService.clearAudit( + TaggingServiceImpl.TAGGING_AUDIT_APPLICATION_NAME, + 0l, System.currentTimeMillis()+1 + ); + return null; + } + }); } public void testTagCRUD() throws Exception { - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // Get the tags - List tags = this.taggingService.getTags(TaggingServiceImplTest.storeRef); - assertNotNull(tags); - assertEquals(0, tags.size()); - - // Create a tag - this.taggingService.createTag(TaggingServiceImplTest.storeRef, TAG_1); - this.taggingService.createTag(TaggingServiceImplTest.storeRef, UPPER_TAG); - - tx.commit(); - tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // Get all the tags - tags = this.taggingService.getTags(TaggingServiceImplTest.storeRef); - assertNotNull(tags); - assertEquals(2, tags.size()); - assertTrue(tags.contains(TAG_1)); - assertTrue(tags.contains(LOWER_TAG)); - - // Check isTag method - assertFalse(this.taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_2)); - assertTrue(this.taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_1)); - assertTrue(this.taggingService.isTag(TaggingServiceImplTest.storeRef, UPPER_TAG)); - assertTrue(this.taggingService.isTag(TaggingServiceImplTest.storeRef, LOWER_TAG)); - - // Remove a tag - this.taggingService.deleteTag(TaggingServiceImplTest.storeRef, UPPER_TAG); - - tx.commit(); - tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // Get all the tags - tags = this.taggingService.getTags(TaggingServiceImplTest.storeRef); - assertNotNull(tags); - assertEquals(1, tags.size()); - assertTrue(tags.contains(TAG_1)); - assertFalse(tags.contains(LOWER_TAG)); - - // Check isTag method - assertFalse(this.taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_2)); - assertTrue(this.taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_1)); - assertFalse(this.taggingService.isTag(TaggingServiceImplTest.storeRef, UPPER_TAG)); - assertFalse(this.taggingService.isTag(TaggingServiceImplTest.storeRef, LOWER_TAG)); - - tx.commit(); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + // Get the tags + List tags = taggingService.getTags(TaggingServiceImplTest.storeRef); + assertNotNull(tags); + assertEquals(0, tags.size()); + + // Create a tag + taggingService.createTag(TaggingServiceImplTest.storeRef, TAG_1); + taggingService.createTag(TaggingServiceImplTest.storeRef, UPPER_TAG); + + return null; + } + }); + + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + // Get all the tags + List tags = taggingService.getTags(TaggingServiceImplTest.storeRef); + assertNotNull(tags); + assertEquals(2, tags.size()); + assertTrue(tags.contains(TAG_1)); + assertTrue(tags.contains(LOWER_TAG)); + + // Check isTag method + assertFalse(taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_2)); + assertTrue(taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_1)); + assertTrue(taggingService.isTag(TaggingServiceImplTest.storeRef, UPPER_TAG)); + assertTrue(taggingService.isTag(TaggingServiceImplTest.storeRef, LOWER_TAG)); + + // Remove a tag + taggingService.deleteTag(TaggingServiceImplTest.storeRef, UPPER_TAG); + return null; + } + }); + + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + // Get all the tags + List tags = taggingService.getTags(TaggingServiceImplTest.storeRef); + assertNotNull(tags); + assertEquals(1, tags.size()); + assertTrue(tags.contains(TAG_1)); + assertFalse(tags.contains(LOWER_TAG)); + + // Check isTag method + assertFalse(taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_2)); + assertTrue(taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_1)); + assertFalse(taggingService.isTag(TaggingServiceImplTest.storeRef, UPPER_TAG)); + assertFalse(taggingService.isTag(TaggingServiceImplTest.storeRef, LOWER_TAG)); + return null; + } + }); } public void testAddRemoveTag() throws Exception { - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - List tags = this.taggingService.getTags(this.document); - assertNotNull(tags); - assertTrue(tags.isEmpty()); - assertFalse(taggingService.hasTag(document, TAG_1)); - - assertTrue(this.taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_1)); - this.taggingService.addTag(this.document, TAG_1); - - tags = this.taggingService.getTags(this.document); - assertNotNull(tags); - assertEquals(1, tags.size()); - assertTrue(tags.contains(TAG_1)); - assertTrue(taggingService.hasTag(document, TAG_1)); - - assertFalse(this.taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_2)); - this.taggingService.addTag(this.document, TAG_2); - - assertTrue(this.taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_2)); - tags = this.taggingService.getTags(this.document); - assertNotNull(tags); - assertEquals(2, tags.size()); - assertTrue(tags.contains(TAG_1)); - assertTrue(tags.contains(TAG_2)); - assertTrue(taggingService.hasTag(document, TAG_1)); - assertTrue(taggingService.hasTag(document, TAG_2)); - - this.taggingService.removeTag(this.document, TAG_1); - tags = this.taggingService.getTags(this.document); - assertNotNull(tags); - assertEquals(1, tags.size()); - assertFalse(tags.contains(TAG_1)); - assertFalse(taggingService.hasTag(document, TAG_1)); - assertTrue(tags.contains(TAG_2)); - assertTrue(taggingService.hasTag(document, TAG_2)); - - List setTags = new ArrayList(2); - setTags.add(TAG_3); - setTags.add(TAG_1); - this.taggingService.setTags(this.document, setTags); - tags = this.taggingService.getTags(this.document); - assertNotNull(tags); - assertEquals(2, tags.size()); - assertTrue(tags.contains(TAG_1)); - assertFalse(tags.contains(TAG_2)); - assertTrue(tags.contains(TAG_3.toLowerCase())); - - this.taggingService.clearTags(this.document); - tags = this.taggingService.getTags(this.document); - assertNotNull(tags); - assertTrue(tags.isEmpty()); - - tx.commit(); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + List tags = taggingService.getTags(document); + assertNotNull(tags); + assertTrue(tags.isEmpty()); + assertFalse(taggingService.hasTag(document, TAG_1)); + + assertTrue(taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_1)); + taggingService.addTag(document, TAG_1); + + tags = taggingService.getTags(document); + assertNotNull(tags); + assertEquals(1, tags.size()); + assertTrue(tags.contains(TAG_1)); + assertTrue(taggingService.hasTag(document, TAG_1)); + + assertFalse(taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_2)); + taggingService.addTag(document, TAG_2); + + assertTrue(taggingService.isTag(TaggingServiceImplTest.storeRef, TAG_2)); + tags = taggingService.getTags(document); + assertNotNull(tags); + assertEquals(2, tags.size()); + assertTrue(tags.contains(TAG_1)); + assertTrue(tags.contains(TAG_2)); + assertTrue(taggingService.hasTag(document, TAG_1)); + assertTrue(taggingService.hasTag(document, TAG_2)); + + taggingService.removeTag(document, TAG_1); + tags = taggingService.getTags(document); + assertNotNull(tags); + assertEquals(1, tags.size()); + assertFalse(tags.contains(TAG_1)); + assertFalse(taggingService.hasTag(document, TAG_1)); + assertTrue(tags.contains(TAG_2)); + assertTrue(taggingService.hasTag(document, TAG_2)); + + List setTags = new ArrayList(2); + setTags.add(TAG_3); + setTags.add(TAG_1); + taggingService.setTags(document, setTags); + tags = taggingService.getTags(document); + assertNotNull(tags); + assertEquals(2, tags.size()); + assertTrue(tags.contains(TAG_1)); + assertFalse(tags.contains(TAG_2)); + assertTrue(tags.contains(TAG_3.toLowerCase())); + + taggingService.clearTags(document); + tags = taggingService.getTags(document); + assertNotNull(tags); + assertTrue(tags.isEmpty()); + return null; + } + }); } public void testTagScopeFindAddRemove() throws Exception { - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // Get scopes for node without - TagScope tagScope = this.taggingService.findTagScope(this.subDocument); - assertNull(tagScope); - List tagScopes = this.taggingService.findAllTagScopes(this.subDocument); - assertNotNull(tagScopes); - assertEquals(0, tagScopes.size()); - - // Add scopes - // TODO should the add return the created scope ?? - this.taggingService.addTagScope(this.folder); - this.taggingService.addTagScope(this.subFolder); - - // Get the scopes - tagScope = this.taggingService.findTagScope(this.subDocument); - assertNotNull(tagScope); - tagScopes = this.taggingService.findAllTagScopes(this.subDocument); - assertNotNull(tagScopes); - assertEquals(2, tagScopes.size()); - tagScope = this.taggingService.findTagScope(this.subFolder); - assertNotNull(tagScope); - tagScopes = this.taggingService.findAllTagScopes(this.subFolder); - assertNotNull(tagScopes); - assertEquals(2, tagScopes.size()); - tagScope = this.taggingService.findTagScope(this.folder); - assertNotNull(tagScope); - tagScopes = this.taggingService.findAllTagScopes(this.folder); - assertNotNull(tagScopes); - assertEquals(1, tagScopes.size()); - - // Remove a scope - this.taggingService.removeTagScope(this.folder); - tagScope = this.taggingService.findTagScope(this.subDocument); - assertNotNull(tagScope); - tagScopes = this.taggingService.findAllTagScopes(this.subDocument); - assertNotNull(tagScopes); - assertEquals(1, tagScopes.size()); - tagScope = this.taggingService.findTagScope(this.subFolder); - assertNotNull(tagScope); - tagScopes = this.taggingService.findAllTagScopes(this.subFolder); - assertNotNull(tagScopes); - assertEquals(1, tagScopes.size()); - tagScope = this.taggingService.findTagScope(this.folder); - assertNull(tagScope); - tagScopes = this.taggingService.findAllTagScopes(this.folder); - assertNotNull(tagScopes); - assertEquals(0, tagScopes.size()); - - tx.commit(); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + // Get scopes for node without + TagScope tagScope = taggingService.findTagScope(subDocument); + assertNull(tagScope); + List tagScopes = taggingService.findAllTagScopes(subDocument); + assertNotNull(tagScopes); + assertEquals(0, tagScopes.size()); + + // Add scopes + // TODO should the add return the created scope ?? + taggingService.addTagScope(folder); + taggingService.addTagScope(subFolder); + + // Get the scopes + tagScope = taggingService.findTagScope(subDocument); + assertNotNull(tagScope); + tagScopes = taggingService.findAllTagScopes(subDocument); + assertNotNull(tagScopes); + assertEquals(2, tagScopes.size()); + tagScope = taggingService.findTagScope(subFolder); + assertNotNull(tagScope); + tagScopes = taggingService.findAllTagScopes(subFolder); + assertNotNull(tagScopes); + assertEquals(2, tagScopes.size()); + tagScope = taggingService.findTagScope(folder); + assertNotNull(tagScope); + tagScopes = taggingService.findAllTagScopes(folder); + assertNotNull(tagScopes); + assertEquals(1, tagScopes.size()); + + // Remove a scope + taggingService.removeTagScope(folder); + tagScope = taggingService.findTagScope(subDocument); + assertNotNull(tagScope); + tagScopes = taggingService.findAllTagScopes(subDocument); + assertNotNull(tagScopes); + assertEquals(1, tagScopes.size()); + tagScope = taggingService.findTagScope(subFolder); + assertNotNull(tagScope); + tagScopes = taggingService.findAllTagScopes(subFolder); + assertNotNull(tagScopes); + assertEquals(1, tagScopes.size()); + tagScope = taggingService.findTagScope(folder); + assertNull(tagScope); + tagScopes = taggingService.findAllTagScopes(folder); + assertNotNull(tagScopes); + assertEquals(0, tagScopes.size()); + return null; + } + }); } public void testTagScope() throws Exception -{ - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // TODO add some tags before the scopes are added - - // Add some tag scopes - this.taggingService.addTagScope(this.folder); - this.taggingService.addTagScope(this.subFolder); + { + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback(){ - // Add some more tags after the scopes have been added - this.taggingService.addTag(this.subDocument, TAG_1); // folder+subfolder - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subDocument, TAG_2); // folder+subfolder - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subDocument, TAG_3); // folder+subfolder - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subFolder, TAG_1); // folder+subfolder - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subFolder, TAG_2); // folder+subfolder - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.folder, TAG_2); // folder only - - tx = asyncOccurs.awaitExecution(tx); - - // re get the tag scopes - TagScope ts1 = this.taggingService.findTagScope(this.subDocument); - TagScope ts2 = this.taggingService.findTagScope(this.folder); - - // Check that the tag scopes got populated - assertEquals( - "Wrong tags on sub folder: " + ts1.getTags(), - 3, ts1.getTags().size() - ); - assertEquals( - "Wrong tags on main folder: " + ts2.getTags(), - 3, ts2.getTags().size() - ); - - // check the order and count of the tagscopes - assertEquals(2, ts1.getTags().get(0).getCount()); - assertEquals(2, ts1.getTags().get(1).getCount()); - assertEquals(1, ts1.getTags().get(2).getCount()); - assertEquals(TAG_1, ts1.getTags().get(0).getName()); - assertEquals(TAG_2, ts1.getTags().get(1).getName()); - assertEquals(TAG_3.toLowerCase(), ts1.getTags().get(2).getName()); - - assertEquals(3, ts2.getTags().get(0).getCount()); - assertEquals(2, ts2.getTags().get(1).getCount()); - assertEquals(1, ts2.getTags().get(2).getCount()); - assertEquals(TAG_2, ts2.getTags().get(0).getName()); - assertEquals(TAG_1, ts2.getTags().get(1).getName()); - assertEquals(TAG_3.toLowerCase(), ts2.getTags().get(2).getName()); - - - // Take some off again - this.taggingService.removeTag(this.folder, TAG_2); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.removeTag(this.subFolder, TAG_2); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.removeTag(this.subFolder, TAG_1); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.removeTag(this.subDocument, TAG_1); - tx = asyncOccurs.awaitExecution(tx); - // And add one more - this.taggingService.addTag(this.folder, TAG_3); - - tx = asyncOccurs.awaitExecution(tx); - - // re get the tag scopes - ts1 = this.taggingService.findTagScope(this.subDocument); - ts2 = this.taggingService.findTagScope(this.folder); - - // Recheck the tag scopes - assertEquals( - "Wrong tags on sub folder: " + ts1.getTags(), - 2, ts1.getTags().size() - ); - assertEquals( - "Wrong tags on main folder: " + ts2.getTags(), - 2, ts2.getTags().size() - ); - - // Sub-folder should be ordered by tag name, as all values 1 - assertEquals(1, ts1.getTags().get(0).getCount()); - assertEquals(1, ts1.getTags().get(1).getCount()); - assertEquals(TAG_2, ts1.getTags().get(0).getName()); - assertEquals(TAG_3.toLowerCase(), ts1.getTags().get(1).getName()); - - // Folder should be still sorted by size, as a 2 & a 1 - assertEquals(2, ts2.getTags().get(0).getCount()); - assertEquals(1, ts2.getTags().get(1).getCount()); - assertEquals(TAG_3.toLowerCase(), ts2.getTags().get(0).getName()); - assertEquals(TAG_2, ts2.getTags().get(1).getName()); - - // Finish - tx.commit(); + @Override + public Void execute() throws Throwable + { + // TODO add some tags before the scopes are added + + // Add some tag scopes + taggingService.addTagScope(folder); + taggingService.addTagScope(subFolder); + + // Add some more tags after the scopes have been added + taggingService.addTag(subDocument, TAG_1); // folder+subfolder + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subDocument, TAG_2); // folder+subfolder + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subDocument, TAG_3); // folder+subfolder + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subFolder, TAG_1); // folder+subfolder + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subFolder, TAG_2); // folder+subfolder + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(folder, TAG_2); // folder only + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // re get the tag scopes + TagScope ts1 = taggingService.findTagScope(subDocument); + TagScope ts2 = taggingService.findTagScope(folder); + + // Check that the tag scopes got populated + assertEquals( + "Wrong tags on sub folder: " + ts1.getTags(), + 3, ts1.getTags().size() + ); + assertEquals( + "Wrong tags on main folder: " + ts2.getTags(), + 3, ts2.getTags().size() + ); + + // check the order and count of the tagscopes + assertEquals(2, ts1.getTags().get(0).getCount()); + assertEquals(2, ts1.getTags().get(1).getCount()); + assertEquals(1, ts1.getTags().get(2).getCount()); + assertEquals(TAG_1, ts1.getTags().get(0).getName()); + assertEquals(TAG_2, ts1.getTags().get(1).getName()); + assertEquals(TAG_3.toLowerCase(), ts1.getTags().get(2).getName()); + + assertEquals(3, ts2.getTags().get(0).getCount()); + assertEquals(2, ts2.getTags().get(1).getCount()); + assertEquals(1, ts2.getTags().get(2).getCount()); + assertEquals(TAG_2, ts2.getTags().get(0).getName()); + assertEquals(TAG_1, ts2.getTags().get(1).getName()); + assertEquals(TAG_3.toLowerCase(), ts2.getTags().get(2).getName()); + + + // Take some off again + taggingService.removeTag(folder, TAG_2); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.removeTag(subFolder, TAG_2); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.removeTag(subFolder, TAG_1); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.removeTag(subDocument, TAG_1); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // And add one more + taggingService.addTag(folder, TAG_3); + return null; + } + }); + + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // re get the tag scopes + TagScope ts1 = taggingService.findTagScope(subDocument); + TagScope ts2 = taggingService.findTagScope(folder); + + // Recheck the tag scopes + assertEquals( + "Wrong tags on sub folder: " + ts1.getTags(), + 2, ts1.getTags().size() + ); + assertEquals( + "Wrong tags on main folder: " + ts2.getTags(), + 2, ts2.getTags().size() + ); + + // Sub-folder should be ordered by tag name, as all values 1 + assertEquals(1, ts1.getTags().get(0).getCount()); + assertEquals(1, ts1.getTags().get(1).getCount()); + assertEquals(TAG_2, ts1.getTags().get(0).getName()); + assertEquals(TAG_3.toLowerCase(), ts1.getTags().get(1).getName()); + + // Folder should be still sorted by size, as a 2 & a 1 + assertEquals(2, ts2.getTags().get(0).getCount()); + assertEquals(1, ts2.getTags().get(1).getCount()); + assertEquals(TAG_3.toLowerCase(), ts2.getTags().get(0).getName()); + assertEquals(TAG_2, ts2.getTags().get(1).getName()); + return null; + } + }); } @SuppressWarnings("unchecked") public void testTagScopeSummary() throws Exception { - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // TODO add some tags before the scopes are added - - // Add some tag scopes - this.taggingService.addTagScope(this.folder); - this.taggingService.addTagScope(this.subFolder); - - // Add some more tags after the scopes have been added - this.taggingService.addTag(this.subDocument, TAG_1); // folder+subfolder - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subDocument, TAG_2); // folder+subfolder - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subDocument, TAG_3); // folder+subfolder - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subFolder, TAG_1); // folder+subfolder - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subFolder, TAG_2); // folder+subfolder - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.folder, TAG_2); // folder only - - tx = asyncOccurs.awaitExecution(tx); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // TODO add some tags before the scopes are added - TagScopePropertyMethodInterceptor.setEnabled(Boolean.TRUE); - - Serializable summaryObj = nodeService.getProperty(this.folder, ContentModel.PROP_TAGSCOPE_SUMMARY); - assertTrue("TagScopeSummary value on main folder is not of correct class: " + summaryObj.getClass().getName(), - List.class.isAssignableFrom(summaryObj.getClass())); - assertEquals(3, ((List)summaryObj).size()); + // Add some tag scopes + taggingService.addTagScope(folder); + taggingService.addTagScope(subFolder); - //Check that the next call for the same summary comes from the cache - Serializable summaryObj2 = nodeService.getProperty(this.folder, ContentModel.PROP_TAGSCOPE_SUMMARY); - assertTrue("TagScopeSummary value on main folder did not come from the cache", - summaryObj == summaryObj2); - - Map props = nodeService.getProperties(this.subFolder); - assertTrue("Properties of subfolder do not include tagScopeSummary", props.containsKey(ContentModel.PROP_TAGSCOPE_SUMMARY)); - summaryObj = props.get(ContentModel.PROP_TAGSCOPE_SUMMARY); - assertTrue("TagScopeSummary value on subfolder is not of correct class: " + summaryObj.getClass().getName(), - List.class.isAssignableFrom(summaryObj.getClass())); - assertEquals(3, ((List)summaryObj).size()); - - TagScopePropertyMethodInterceptor.setEnabled(Boolean.FALSE); - - summaryObj = nodeService.getProperty(this.folder, ContentModel.PROP_TAGSCOPE_SUMMARY); - assertNull("TagScopeSummary value on main folder should be null: " + summaryObj, - summaryObj); - - props = nodeService.getProperties(this.subFolder); - assertFalse("Properties of subfolder should not contain tagScopeProperty", props.containsKey(ContentModel.PROP_TAGSCOPE_SUMMARY)); - - // Finish - tx.commit(); + // Add some more tags after the scopes have been added + taggingService.addTag(subDocument, TAG_1); // folder+subfolder + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subDocument, TAG_2); // folder+subfolder + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subDocument, TAG_3); // folder+subfolder + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subFolder, TAG_1); // folder+subfolder + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subFolder, TAG_2); // folder+subfolder + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(folder, TAG_2); // folder only + return null; + } + }); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + TagScopePropertyMethodInterceptor.setEnabled(Boolean.TRUE); + + Serializable summaryObj = nodeService.getProperty(folder, ContentModel.PROP_TAGSCOPE_SUMMARY); + assertTrue("TagScopeSummary value on main folder is not of correct class: " + summaryObj.getClass().getName(), + List.class.isAssignableFrom(summaryObj.getClass())); + assertEquals(3, ((List)summaryObj).size()); + + //Check that the next call for the same summary comes from the cache + Serializable summaryObj2 = nodeService.getProperty(folder, ContentModel.PROP_TAGSCOPE_SUMMARY); + assertTrue("TagScopeSummary value on main folder did not come from the cache", + summaryObj == summaryObj2); + + Map props = nodeService.getProperties(subFolder); + assertTrue("Properties of subfolder do not include tagScopeSummary", props.containsKey(ContentModel.PROP_TAGSCOPE_SUMMARY)); + summaryObj = props.get(ContentModel.PROP_TAGSCOPE_SUMMARY); + assertTrue("TagScopeSummary value on subfolder is not of correct class: " + summaryObj.getClass().getName(), + List.class.isAssignableFrom(summaryObj.getClass())); + assertEquals(3, ((List)summaryObj).size()); + + TagScopePropertyMethodInterceptor.setEnabled(Boolean.FALSE); + + summaryObj = nodeService.getProperty(folder, ContentModel.PROP_TAGSCOPE_SUMMARY); + assertNull("TagScopeSummary value on main folder should be null: " + summaryObj, + summaryObj); + + props = nodeService.getProperties(subFolder); + assertFalse("Properties of subfolder should not contain tagScopeProperty", props.containsKey(ContentModel.PROP_TAGSCOPE_SUMMARY)); + return null; + } + }); } public void testTagScopeRefresh() throws Exception { - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // Add some tags to the nodes - // tag scope on folder should be .... - // tag2 = 3 - // tag1 = 2 - // tag3 = 1 - this.taggingService.addTag(this.subDocument, TAG_1); - this.taggingService.addTag(this.subDocument, TAG_2); - this.taggingService.addTag(this.subDocument, TAG_3); - this.taggingService.addTag(this.subFolder, TAG_1); - this.taggingService.addTag(this.subFolder, TAG_2); - this.taggingService.addTag(this.folder, TAG_2); - - // Commit the changes. No action will fire, as there - // aren't currently any tag scopes to be updated! - tx.commit(); - tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // Add the tag scope - this.taggingService.addTagScope(this.folder); - - // The updates in this case are synchronous, not - // async, so we don't need to wait for any actions - tx.commit(); - tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // Get the tag scope and check that all the values have been set correctly - TagScope tagScope = this.taggingService.findTagScope(this.folder); - assertNotNull(tagScope); - assertEquals(3, tagScope.getTags().size()); - assertEquals(3, tagScope.getTag(TAG_2).getCount()); - assertEquals(2, tagScope.getTag(TAG_1).getCount()); - assertEquals(1, tagScope.getTag(TAG_3.toLowerCase()).getCount()); - - // Finish - tx.commit(); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Add some tags to the nodes + // tag scope on folder should be .... + // tag2 = 3 + // tag1 = 2 + // tag3 = 1 + taggingService.addTag(subDocument, TAG_1); + taggingService.addTag(subDocument, TAG_2); + taggingService.addTag(subDocument, TAG_3); + taggingService.addTag(subFolder, TAG_1); + taggingService.addTag(subFolder, TAG_2); + taggingService.addTag(folder, TAG_2); + + // Commit the changes. No action will fire, as there + // aren't currently any tag scopes to be updated! + return null; + } + }); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Add the tag scope + taggingService.addTagScope(folder); + + // The updates in this case are synchronous, not + // async, so we don't need to wait for any actions + return null; + } + }); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Get the tag scope and check that all the values have been set correctly + TagScope tagScope = taggingService.findTagScope(folder); + assertNotNull(tagScope); + assertEquals(3, tagScope.getTags().size()); + assertEquals(3, tagScope.getTag(TAG_2).getCount()); + assertEquals(2, tagScope.getTag(TAG_1).getCount()); + assertEquals(1, tagScope.getTag(TAG_3.toLowerCase()).getCount()); + return null; + } + }); } public void testTagScopeSetUpdate() throws Exception { - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // Set up tag scope - this.taggingService.addTagScope(this.folder);; - - // Add some tags - this.taggingService.addTag(this.folder, TAG_1); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.document, TAG_1); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.document, TAG_2); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subDocument, TAG_1); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subDocument, TAG_2); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subDocument, TAG_3); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subFolder, TAG_1); - tx = asyncOccurs.awaitExecution(tx); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Set up tag scope + taggingService.addTagScope(folder); + ; - // Check that tag scope - TagScope ts1 = this.taggingService.findTagScope(this.folder); - assertEquals( - "Wrong tags on folder tagscope: " + ts1.getTags(), - 3, ts1.getTags().size() - ); - assertEquals(4, ts1.getTag(TAG_1).getCount()); - assertEquals(2, ts1.getTag(TAG_2).getCount()); - assertEquals(1, ts1.getTag(TAG_3.toLowerCase()).getCount()); - - // Re-set the tag scopes - List tags = new ArrayList(3); - tags.add(TAG_2); - tags.add(TAG_3); - tags.add(TAG_4); - - this.taggingService.setTags(this.subDocument, tags); - tx = asyncOccurs.awaitExecution(tx); - - // Check that the tagscope has been updated correctly - ts1 = this.taggingService.findTagScope(this.folder); - assertEquals(3, ts1.getTag(TAG_1).getCount()); - assertEquals(2, ts1.getTag(TAG_2).getCount()); - assertEquals(1, ts1.getTag(TAG_3.toLowerCase()).getCount()); - assertEquals(1, ts1.getTag(TAG_4).getCount()); + // Add some tags + taggingService.addTag(folder, TAG_1); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(document, TAG_1); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(document, TAG_2); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subDocument, TAG_1); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subDocument, TAG_2); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subDocument, TAG_3); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subFolder, TAG_1); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Check that tag scope + TagScope ts1 = taggingService.findTagScope(folder); + assertEquals("Wrong tags on folder tagscope: " + ts1.getTags(), 3, ts1.getTags().size()); + assertEquals(4, ts1.getTag(TAG_1).getCount()); + assertEquals(2, ts1.getTag(TAG_2).getCount()); + assertEquals(1, ts1.getTag(TAG_3.toLowerCase()).getCount()); - // Finish - tx.commit(); + // Re-set the tag scopes + List tags = new ArrayList(3); + tags.add(TAG_2); + tags.add(TAG_3); + tags.add(TAG_4); + + taggingService.setTags(subDocument, tags); + return null; + } + }); + + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Check that the tagscope has been updated correctly + TagScope ts1 = taggingService.findTagScope(folder); + assertEquals(3, ts1.getTag(TAG_1).getCount()); + assertEquals(2, ts1.getTag(TAG_2).getCount()); + assertEquals(1, ts1.getTag(TAG_3.toLowerCase()).getCount()); + assertEquals(1, ts1.getTag(TAG_4).getCount()); + return null; + } + }); } /* @@ -716,38 +924,39 @@ public class TaggingServiceImplTest extends TestCase { // Add tag scope to a folder, then add a non-ASCII (unicode) // tag onto the folder - transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback() - { - public Void execute() throws Throwable - { - taggingService.addTagScope(folder); - taggingService.addTag(folder, TAG_I18N); - return null; - } - }, false, true - ); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTagScope(folder); + taggingService.addTag(folder, TAG_I18N); + return null; + } + }); - // Wait for the tagging service to process the tag change - UserTransaction tx = asyncOccurs.awaitExecution(null); - - // Get the tag from the node - List tags = this.taggingService.getTags(this.folder); - assertNotNull(tags); - assertEquals(1, tags.size()); - assertEquals(TAG_I18N, tags.get(0)); - - // Get the tag from the tagscope - TagScope tagScope = this.taggingService.findTagScope(this.folder); - assertNotNull(tagScope); - assertEquals(1, tagScope.getTags().size()); - TagDetails tagDetails = tagScope.getTag(TAG_I18N); - assertNotNull(tagDetails); - assertEquals(TAG_I18N, tagDetails.getName()); - assertEquals(1, tagDetails.getCount()); - - // Finish - tx.commit(); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Get the tag from the node + List tags = taggingService.getTags(folder); + assertNotNull(tags); + assertEquals(1, tags.size()); + assertEquals(TAG_I18N, tags.get(0)); + + // Get the tag from the tagscope + TagScope tagScope = taggingService.findTagScope(folder); + assertNotNull(tagScope); + assertEquals(1, tagScope.getTags().size()); + TagDetails tagDetails = tagScope.getTag(TAG_I18N); + assertNotNull(tagDetails); + assertEquals(TAG_I18N, tagDetails.getName()); + assertEquals(1, tagDetails.getCount()); + return null; + } + }); } /** @@ -756,319 +965,429 @@ public class TaggingServiceImplTest extends TestCase * moved, copied and deleted. */ public void testTagScopeUpdateViaNodePolicies() throws Exception { - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // The various tags we'll be using in testing - NodeRef tagFoo1 = taggingService.createTag(folder.getStoreRef(), "Foo1"); - NodeRef tagFoo2 = taggingService.createTag(folder.getStoreRef(), "Foo2"); - NodeRef tagFoo3 = taggingService.createTag(folder.getStoreRef(), "Foo3"); - NodeRef tagBar = taggingService.createTag(folder.getStoreRef(), "Bar"); - - List tagsList = new ArrayList(); - - - // Create two containers marked as tag scopes - Map container1Props = new HashMap(1); - container1Props.put(ContentModel.PROP_NAME, "Container1"); - NodeRef container1 = this.nodeService.createNode( - folder, - ContentModel.ASSOC_CONTAINS, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - container1Props).getChildRef(); - assertEquals(0, nodeService.getChildAssocs(container1).size()); - - Map container2Props = new HashMap(1); - container1Props.put(ContentModel.PROP_NAME, "Container2"); - NodeRef container2 = this.nodeService.createNode( - folder, - ContentModel.ASSOC_CONTAINS, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - container2Props).getChildRef(); - assertEquals(0, nodeService.getChildAssocs(container2).size()); - - - // Check that the tag scopes are empty - taggingService.addTagScope(container1); - taggingService.addTagScope(container2); - tx = asyncOccurs.awaitExecution(tx); - assertTrue( taggingService.isTagScope(container1) ); - assertTrue( taggingService.isTagScope(container2) ); - assertEquals(0, taggingService.findTagScope(container1).getTags().size()); - assertEquals(0, taggingService.findTagScope(container2).getTags().size()); - - - // Create a folder, without any tags on it - Map taggedFolderProps = new HashMap(1); - taggedFolderProps.put(ContentModel.PROP_NAME, "Folder"); - NodeRef taggedFolder = this.nodeService.createNode( - container1, - ContentModel.ASSOC_CONTAINS, - ContentModel.ASSOC_CHILDREN, - ContentModel.TYPE_FOLDER, - taggedFolderProps).getChildRef(); - - // No tags, so no changes should have occured, and no actions - tx.commit(); - tx = transactionService.getUserTransaction(); - tx.begin(); - assertEquals(0, taggingService.findTagScope(container1).getTags().size()); - assertEquals(0, taggingService.findTagScope(container2).getTags().size()); - assertEquals(1, nodeService.getChildAssocs(container1).size()); - - - // Now update the folder to add in tags - taggedFolderProps.clear(); - tagsList.clear(); - - tagsList.add(tagFoo1); - tagsList.add(tagFoo3); - taggedFolderProps.put(ContentModel.ASPECT_TAGGABLE, (Serializable)tagsList); - this.nodeService.addProperties(taggedFolder, taggedFolderProps); - - tx = asyncOccurs.awaitExecution(tx); - assertEquals( - "Unexpected tags " + taggingService.findTagScope(container1).getTags(), - 2, taggingService.findTagScope(container1).getTags().size() - ); - assertEquals( - "Unexpected tags " + taggingService.findTagScope(container2).getTags(), - 0, taggingService.findTagScope(container2).getTags().size() - ); - - assertEquals(1, taggingService.findTagScope(container1).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo3").getCount()); - - - // Create a document within it, check that tag scope tags are updated - tagsList.clear(); - tagsList.add(tagFoo1); - tagsList.add(tagFoo2); - - Map taggedDocProps = new HashMap(1); - taggedDocProps.put(ContentModel.PROP_NAME, "Document"); - taggedDocProps.put(ContentModel.ASPECT_TAGGABLE, (Serializable)tagsList); - NodeRef taggedDoc = this.nodeService.createNode( - taggedFolder, - ContentModel.ASSOC_CONTAINS, - ContentModel.ASPECT_TAGGABLE, - ContentModel.TYPE_CONTENT, - taggedDocProps).getChildRef(); - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(3, taggingService.findTagScope(container1).getTags().size()); - assertEquals(0, taggingService.findTagScope(container2).getTags().size()); - - assertEquals(2, taggingService.findTagScope(container1).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo3").getCount()); - - - // Check that the Document really is a child of the folder, - // otherwise later checks will fail for really odd reasons - assertEquals(1, nodeService.getChildAssocs(container1).size()); - assertEquals(1, nodeService.getChildAssocs(taggedFolder).size()); - - - // Check out the node - // Tags should be doubled up. (We don't care about ContentModel.ASPECT_WORKING_COPY - // because it isn't applied at suitable times to take not of) - NodeRef checkedOutDoc = checkOutCheckInService.checkout(taggedDoc); - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(3, taggingService.findTagScope(container1).getTags().size()); - assertEquals(0, taggingService.findTagScope(container2).getTags().size()); - - assertEquals(3, taggingService.findTagScope(container1).getTag("foo1").getCount()); - assertEquals(2, taggingService.findTagScope(container1).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo3").getCount()); - - assertEquals(1, nodeService.getChildAssocs(container1).size()); - assertEquals(2, nodeService.getChildAssocs(taggedFolder).size()); - - - // And check it back in again - // Tags should go back to how they were - checkOutCheckInService.checkin(checkedOutDoc, null); - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(3, taggingService.findTagScope(container1).getTags().size()); - assertEquals(0, taggingService.findTagScope(container2).getTags().size()); - - assertEquals(2, taggingService.findTagScope(container1).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo3").getCount()); - - assertEquals(1, nodeService.getChildAssocs(container1).size()); - assertEquals(1, nodeService.getChildAssocs(taggedFolder).size()); + class TestData + { + public NodeRef tagFoo1; + public NodeRef tagFoo2; + public NodeRef tagFoo3; + public NodeRef tagBar; + public NodeRef container1; + public NodeRef container2; + public NodeRef taggedFolder; + public NodeRef taggedFolder2; + public NodeRef taggedDoc; + public NodeRef taggedDoc2; + public NodeRef checkedOutDoc; + } + final TestData testData = new TestData(); + + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // The various tags we'll be using in testing + testData.tagFoo1 = taggingService.createTag(folder.getStoreRef(), "Foo1"); + testData.tagFoo2 = taggingService.createTag(folder.getStoreRef(), "Foo2"); + testData.tagFoo3 = taggingService.createTag(folder.getStoreRef(), "Foo3"); + testData.tagBar = taggingService.createTag(folder.getStoreRef(), "Bar"); + + List tagsList = new ArrayList(); + + + // Create two containers marked as tag scopes + Map container1Props = new HashMap(1); + container1Props.put(ContentModel.PROP_NAME, "Container1"); + testData.container1 = nodeService.createNode( + folder, + ContentModel.ASSOC_CONTAINS, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + container1Props).getChildRef(); + assertEquals(0, nodeService.getChildAssocs(testData.container1).size()); + + Map container2Props = new HashMap(1); + container1Props.put(ContentModel.PROP_NAME, "Container2"); + testData.container2 = nodeService.createNode( + folder, + ContentModel.ASSOC_CONTAINS, + ContentModel.ASSOC_CHILDREN, + ContentModel.TYPE_FOLDER, + container2Props).getChildRef(); + assertEquals(0, nodeService.getChildAssocs(testData.container2).size()); + + + // Check that the tag scopes are empty + taggingService.addTagScope(testData.container1); + taggingService.addTagScope(testData.container2); + return null; + } + }); + this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertTrue(taggingService.isTagScope(testData.container1)); + assertTrue(taggingService.isTagScope(testData.container2)); + assertEquals(0, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(0, taggingService.findTagScope(testData.container2).getTags().size()); - - // Do a node->node copy of the document onto the other container - taggedDocProps.clear(); - taggedDocProps.put(ContentModel.PROP_NAME, "CopyDoc"); - assertEquals(0, nodeService.getChildAssocs(container2).size()); - NodeRef taggedDoc2 = this.nodeService.createNode( - container2, - ContentModel.ASSOC_CONTAINS, - ContentModel.ASPECT_TAGGABLE, - ContentModel.TYPE_CONTENT, - taggedDocProps).getChildRef(); - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(3, taggingService.findTagScope(container1).getTags().size()); - assertEquals(0, taggingService.findTagScope(container2).getTags().size()); - assertEquals(1, nodeService.getChildAssocs(container2).size()); - - copyService.copy(taggedDoc, taggedDoc2); - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(3, taggingService.findTagScope(container1).getTags().size()); - assertEquals(2, taggingService.findTagScope(container2).getTags().size()); - - assertEquals(2, taggingService.findTagScope(container1).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo3").getCount()); - - assertEquals(1, taggingService.findTagScope(container2).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container2).getTag("foo2").getCount()); - - // Check that things were fine after the copy - assertEquals(1, nodeService.getChildAssocs(container2).size()); - assertEquals(container2, nodeService.getPrimaryParent(taggedDoc2).getParentRef()); - assertEquals(container2, taggingService.findTagScope(taggedDoc2).getNodeRef()); - - - // Copy the folder to another container - // Does a proper, recursing copy - NodeRef taggedFolder2 = copyService.copy( - taggedFolder, container2, - ContentModel.ASSOC_CONTAINS, - ContentModel.ASSOC_CHILDREN, - true); - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(3, taggingService.findTagScope(container1).getTags().size()); - assertEquals(3, taggingService.findTagScope(container2).getTags().size()); - assertEquals(2, nodeService.getChildAssocs(container2).size()); - assertEquals(1, nodeService.getChildAssocs(taggedFolder2).size()); - - assertEquals(2, taggingService.findTagScope(container1).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo3").getCount()); - - assertEquals(3, taggingService.findTagScope(container2).getTag("foo1").getCount()); - assertEquals(2, taggingService.findTagScope(container2).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container2).getTag("foo3").getCount()); - - - // Update the document on the original - tagsList.clear(); - tagsList.add(tagBar); - taggedDocProps.clear(); - taggedDocProps.put(ContentModel.ASPECT_TAGGABLE, (Serializable)tagsList); - taggedDocProps.put(ContentModel.PROP_NAME, "UpdatedDocument"); - this.nodeService.addProperties(taggedDoc, taggedDocProps); - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(3, taggingService.findTagScope(container1).getTags().size()); - assertEquals(3, taggingService.findTagScope(container2).getTags().size()); - - assertEquals(1, taggingService.findTagScope(container1).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo3").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("bar").getCount()); - - assertEquals(3, taggingService.findTagScope(container2).getTag("foo1").getCount()); - assertEquals(2, taggingService.findTagScope(container2).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container2).getTag("foo3").getCount()); + // Create a folder, without any tags on it + Map taggedFolderProps = new HashMap(1); + taggedFolderProps.put(ContentModel.PROP_NAME, "Folder"); + testData.taggedFolder = nodeService.createNode(testData.container1, ContentModel.ASSOC_CONTAINS, + ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_FOLDER, taggedFolderProps).getChildRef(); - - // Move the document to another container - taggedDoc = nodeService.moveNode(taggedDoc, container2, - ContentModel.ASSOC_CONTAINS, - ContentModel.ASPECT_TAGGABLE).getChildRef(); - tx = asyncOccurs.awaitExecution(tx); - assertEquals(2, taggingService.findTagScope(container1).getTags().size()); - assertEquals(4, taggingService.findTagScope(container2).getTags().size()); - - assertEquals(1, taggingService.findTagScope(container1).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo3").getCount()); - - assertEquals(3, taggingService.findTagScope(container2).getTag("foo1").getCount()); - assertEquals(2, taggingService.findTagScope(container2).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container2).getTag("foo3").getCount()); - assertEquals(1, taggingService.findTagScope(container2).getTag("bar").getCount()); + // No tags, so no changes should have occured, and no actions + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(0, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(0, taggingService.findTagScope(testData.container2).getTags().size()); + assertEquals(1, nodeService.getChildAssocs(testData.container1).size()); - - // Check the state of the tree - assertEquals(1, nodeService.getChildAssocs(container1).size()); - assertEquals(3, nodeService.getChildAssocs(container2).size()); + // Now update the folder to add in tags + Map taggedFolderProps = new HashMap(1); + List tagsList = new ArrayList(); - assertEquals(container2, nodeService.getPrimaryParent(taggedDoc).getParentRef()); - assertEquals(container2, taggingService.findTagScope(taggedDoc).getNodeRef()); - assertEquals(container2, nodeService.getPrimaryParent(taggedDoc2).getParentRef()); - assertEquals(container2, taggingService.findTagScope(taggedDoc2).getNodeRef()); - - - // Delete the documents and folder one at a time - nodeService.deleteNode(taggedDoc); // container 2, "bar" - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(2, taggingService.findTagScope(container1).getTags().size()); - assertEquals(3, taggingService.findTagScope(container2).getTags().size()); - assertEquals(1, nodeService.getChildAssocs(container1).size()); - assertEquals(2, nodeService.getChildAssocs(container2).size()); - - assertEquals(1, taggingService.findTagScope(container1).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo3").getCount()); - - assertEquals(3, taggingService.findTagScope(container2).getTag("foo1").getCount()); - assertEquals(2, taggingService.findTagScope(container2).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container2).getTag("foo3").getCount()); - assertEquals(null, taggingService.findTagScope(container2).getTag("bar")); - - - nodeService.deleteNode(taggedDoc2); // container 2, "foo1", "foo2" - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(2, taggingService.findTagScope(container1).getTags().size()); - assertEquals(3, taggingService.findTagScope(container2).getTags().size()); - assertEquals(1, nodeService.getChildAssocs(container1).size()); - assertEquals(1, nodeService.getChildAssocs(container2).size()); - - assertEquals(1, taggingService.findTagScope(container1).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container1).getTag("foo3").getCount()); - - assertEquals(2, taggingService.findTagScope(container2).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container2).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container2).getTag("foo3").getCount()); - assertEquals(null, taggingService.findTagScope(container2).getTag("bar")); - - - nodeService.deleteNode(taggedFolder); // container 1, "foo1", "foo3" - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(0, taggingService.findTagScope(container1).getTags().size()); - assertEquals(3, taggingService.findTagScope(container2).getTags().size()); - assertEquals(0, nodeService.getChildAssocs(container1).size()); - assertEquals(1, nodeService.getChildAssocs(container2).size()); - - assertEquals(2, taggingService.findTagScope(container2).getTag("foo1").getCount()); - assertEquals(1, taggingService.findTagScope(container2).getTag("foo2").getCount()); - assertEquals(1, taggingService.findTagScope(container2).getTag("foo3").getCount()); - - - nodeService.deleteNode(taggedFolder2); // container 2, has a child also - - tx = asyncOccurs.awaitExecution(tx); - assertEquals(0, taggingService.findTagScope(container1).getTags().size()); - assertEquals(0, taggingService.findTagScope(container2).getTags().size()); - assertEquals(0, nodeService.getChildAssocs(container1).size()); - assertEquals(0, nodeService.getChildAssocs(container2).size()); - - // Finish - tx.commit(); + tagsList.add(testData.tagFoo1); + tagsList.add(testData.tagFoo3); + taggedFolderProps.put(ContentModel.ASPECT_TAGGABLE, (Serializable) tagsList); + nodeService.addProperties(testData.taggedFolder, taggedFolderProps); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals( + "Unexpected tags " + taggingService.findTagScope(testData.container1).getTags(), + 2, taggingService.findTagScope(testData.container1).getTags().size() + ); + assertEquals( + "Unexpected tags " + taggingService.findTagScope(testData.container2).getTags(), + 0, taggingService.findTagScope(testData.container2).getTags().size() + ); + + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo3").getCount()); + + + // Create a document within it, check that tag scope tags are updated + List tagsList = new ArrayList(); + tagsList.add(testData.tagFoo1); + tagsList.add(testData.tagFoo2); + + Map taggedDocProps = new HashMap(1); + taggedDocProps.put(ContentModel.PROP_NAME, "Document"); + taggedDocProps.put(ContentModel.ASPECT_TAGGABLE, (Serializable)tagsList); + testData.taggedDoc = nodeService.createNode( + testData.taggedFolder, + ContentModel.ASSOC_CONTAINS, + ContentModel.ASPECT_TAGGABLE, + ContentModel.TYPE_CONTENT, + taggedDocProps).getChildRef(); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(3, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(0, taggingService.findTagScope(testData.container2).getTags().size()); + + assertEquals(2, taggingService.findTagScope(testData.container1).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo3").getCount()); + + + // Check that the Document really is a child of the folder, + // otherwise later checks will fail for really odd reasons + assertEquals(1, nodeService.getChildAssocs(testData.container1).size()); + assertEquals(1, nodeService.getChildAssocs(testData.taggedFolder).size()); + + + // Check out the node + // Tags should be doubled up. (We don't care about ContentModel.ASPECT_WORKING_COPY + // because it isn't applied at suitable times to take not of) + testData.checkedOutDoc = checkOutCheckInService.checkout(testData.taggedDoc); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(3, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(0, taggingService.findTagScope(testData.container2).getTags().size()); + + assertEquals(3, taggingService.findTagScope(testData.container1).getTag("foo1").getCount()); + assertEquals(2, taggingService.findTagScope(testData.container1).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo3").getCount()); + + assertEquals(1, nodeService.getChildAssocs(testData.container1).size()); + assertEquals(2, nodeService.getChildAssocs(testData.taggedFolder).size()); + + + // And check it back in again + // Tags should go back to how they were + checkOutCheckInService.checkin(testData.checkedOutDoc, null); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(3, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(0, taggingService.findTagScope(testData.container2).getTags().size()); + + assertEquals(2, taggingService.findTagScope(testData.container1).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo3").getCount()); + + assertEquals(1, nodeService.getChildAssocs(testData.container1).size()); + assertEquals(1, nodeService.getChildAssocs(testData.taggedFolder).size()); + + + // Do a node->node copy of the document onto the other container + Map taggedDocProps = new HashMap(1); + taggedDocProps.put(ContentModel.PROP_NAME, "CopyDoc"); + assertEquals(0, nodeService.getChildAssocs(testData.container2).size()); + testData.taggedDoc2 = nodeService.createNode( + testData.container2, + ContentModel.ASSOC_CONTAINS, + ContentModel.ASPECT_TAGGABLE, + ContentModel.TYPE_CONTENT, + taggedDocProps).getChildRef(); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(3, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(0, taggingService.findTagScope(testData.container2).getTags().size()); + assertEquals(1, nodeService.getChildAssocs(testData.container2).size()); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + copyService.copy(testData.taggedDoc, testData.taggedDoc2); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(3, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(2, taggingService.findTagScope(testData.container2).getTags().size()); + + assertEquals(2, taggingService.findTagScope(testData.container1).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo3").getCount()); + + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("foo2").getCount()); + + // Check that things were fine after the copy + assertEquals(1, nodeService.getChildAssocs(testData.container2).size()); + assertEquals(testData.container2, nodeService.getPrimaryParent(testData.taggedDoc2).getParentRef()); + assertEquals(testData.container2, taggingService.findTagScope(testData.taggedDoc2).getNodeRef()); + + + // Copy the folder to another container + // Does a proper, recursing copy + testData.taggedFolder2 = copyService.copy( + testData.taggedFolder, testData.container2, + ContentModel.ASSOC_CONTAINS, + ContentModel.ASSOC_CHILDREN, + true); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(3, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(3, taggingService.findTagScope(testData.container2).getTags().size()); + assertEquals(2, nodeService.getChildAssocs(testData.container2).size()); + assertEquals(1, nodeService.getChildAssocs(testData.taggedFolder2).size()); + + assertEquals(2, taggingService.findTagScope(testData.container1).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo3").getCount()); + + assertEquals(3, taggingService.findTagScope(testData.container2).getTag("foo1").getCount()); + assertEquals(2, taggingService.findTagScope(testData.container2).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("foo3").getCount()); + + + // Update the document on the original + List tagsList = new ArrayList(); + tagsList.add(testData.tagBar); + Map taggedDocProps = new HashMap(1); + taggedDocProps.put(ContentModel.ASPECT_TAGGABLE, (Serializable)tagsList); + taggedDocProps.put(ContentModel.PROP_NAME, "UpdatedDocument"); + nodeService.addProperties(testData.taggedDoc, taggedDocProps); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(3, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(3, taggingService.findTagScope(testData.container2).getTags().size()); + + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo3").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("bar").getCount()); + + assertEquals(3, taggingService.findTagScope(testData.container2).getTag("foo1").getCount()); + assertEquals(2, taggingService.findTagScope(testData.container2).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("foo3").getCount()); + + + // Move the document to another container + testData.taggedDoc = nodeService.moveNode(testData.taggedDoc, testData.container2, + ContentModel.ASSOC_CONTAINS, + ContentModel.ASPECT_TAGGABLE).getChildRef(); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(2, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(4, taggingService.findTagScope(testData.container2).getTags().size()); + + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo3").getCount()); + + assertEquals(3, taggingService.findTagScope(testData.container2).getTag("foo1").getCount()); + assertEquals(2, taggingService.findTagScope(testData.container2).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("foo3").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("bar").getCount()); + + + // Check the state of the tree + assertEquals(1, nodeService.getChildAssocs(testData.container1).size()); + assertEquals(3, nodeService.getChildAssocs(testData.container2).size()); + + assertEquals(testData.container2, nodeService.getPrimaryParent(testData.taggedDoc).getParentRef()); + assertEquals(testData.container2, taggingService.findTagScope(testData.taggedDoc).getNodeRef()); + assertEquals(testData.container2, nodeService.getPrimaryParent(testData.taggedDoc2).getParentRef()); + assertEquals(testData.container2, taggingService.findTagScope(testData.taggedDoc2).getNodeRef()); + + + // Delete the documents and folder one at a time + nodeService.deleteNode(testData.taggedDoc); // container 2, "bar" + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(2, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(3, taggingService.findTagScope(testData.container2).getTags().size()); + assertEquals(1, nodeService.getChildAssocs(testData.container1).size()); + assertEquals(2, nodeService.getChildAssocs(testData.container2).size()); + + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo3").getCount()); + + assertEquals(3, taggingService.findTagScope(testData.container2).getTag("foo1").getCount()); + assertEquals(2, taggingService.findTagScope(testData.container2).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("foo3").getCount()); + assertEquals(null, taggingService.findTagScope(testData.container2).getTag("bar")); + + + nodeService.deleteNode(testData.taggedDoc2); // container 2, "foo1", "foo2" + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(2, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(3, taggingService.findTagScope(testData.container2).getTags().size()); + assertEquals(1, nodeService.getChildAssocs(testData.container1).size()); + assertEquals(1, nodeService.getChildAssocs(testData.container2).size()); + + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container1).getTag("foo3").getCount()); + + assertEquals(2, taggingService.findTagScope(testData.container2).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("foo3").getCount()); + assertEquals(null, taggingService.findTagScope(testData.container2).getTag("bar")); + + + nodeService.deleteNode(testData.taggedFolder); // container 1, "foo1", "foo3" + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(0, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(3, taggingService.findTagScope(testData.container2).getTags().size()); + assertEquals(0, nodeService.getChildAssocs(testData.container1).size()); + assertEquals(1, nodeService.getChildAssocs(testData.container2).size()); + + assertEquals(2, taggingService.findTagScope(testData.container2).getTag("foo1").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("foo2").getCount()); + assertEquals(1, taggingService.findTagScope(testData.container2).getTag("foo3").getCount()); + + + nodeService.deleteNode(testData.taggedFolder2); // container 2, has a child also + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertEquals(0, taggingService.findTagScope(testData.container1).getTags().size()); + assertEquals(0, taggingService.findTagScope(testData.container2).getTags().size()); + assertEquals(0, nodeService.getChildAssocs(testData.container1).size()); + assertEquals(0, nodeService.getChildAssocs(testData.container2).size()); + return null; + } + }); } /** @@ -1080,170 +1399,248 @@ public class TaggingServiceImplTest extends TestCase */ public void testPermissionsAndPolicies() throws Exception { + class TestData + { + public NodeRef taggedNode; + public NodeRef auditableFolder; + public Date origModified; + } + final TestData testData = new TestData(); + final String USER_1 = "User1"; authenticationComponent.setSystemUserAsCurrentUser(); - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // Create a user - String USER_1 = "User1"; - if(authenticationService.authenticationExists(USER_1)) - authenticationService.deleteAuthentication(USER_1); - if(personService.personExists(USER_1)) - personService.deletePerson(USER_1); - - authenticationService.createAuthentication(USER_1, "PWD".toCharArray()); - PropertyMap personProperties = new PropertyMap(); - personProperties.put(ContentModel.PROP_USERNAME, USER_1); - personProperties.put(ContentModel.PROP_AUTHORITY_DISPLAY_NAME, "title" + USER_1); - personProperties.put(ContentModel.PROP_FIRSTNAME, "firstName"); - personProperties.put(ContentModel.PROP_LASTNAME, "lastName"); - personProperties.put(ContentModel.PROP_EMAIL, USER_1+"@example.com"); - personProperties.put(ContentModel.PROP_JOBTITLE, "jobTitle"); - personService.createPerson(personProperties); - - - // Give that user permissions on the tagging category root, so - // they're allowed to add new tags - NodeRef tn = taggingService.createTag(folder.getStoreRef(), "Testing"); - NodeRef tr = nodeService.getPrimaryParent(tn).getParentRef(); - permissionService.setPermission(tr, USER_1, PermissionService.EDITOR, true); - permissionService.setPermission(tr, USER_1, PermissionService.CONTRIBUTOR, true); - - - // Create a folder with a tag scope on it + auditable aspect - // User can read but not write - authenticationComponent.setSystemUserAsCurrentUser(); - NodeRef auditableFolder = nodeService.createNode( - folder, ContentModel.ASSOC_CONTAINS, - QName.createQName("Folder"), ContentModel.TYPE_FOLDER - ).getChildRef(); - nodeService.addAspect(auditableFolder, ContentModel.ASPECT_AUDITABLE, null); - taggingService.addTagScope(auditableFolder); - permissionService.setPermission(auditableFolder, USER_1, PermissionService.CONSUMER, true); - - // Auditable checks - assertEquals("System", nodeService.getProperty(auditableFolder, ContentModel.PROP_CREATOR)); - assertEquals("System", nodeService.getProperty(auditableFolder, ContentModel.PROP_MODIFIER)); - Date origModified = (Date)nodeService.getProperty(auditableFolder, ContentModel.PROP_MODIFIED); - - - // Create a node without tags, which the user - // can write to - NodeRef taggedNode = nodeService.createNode( - auditableFolder, ContentModel.ASSOC_CONTAINS, - QName.createQName("Tagged"), ContentModel.TYPE_CONTENT - ).getChildRef(); - permissionService.setPermission(taggedNode, USER_1, PermissionService.EDITOR, true); - - - // Tag the node as the user - authenticationComponent.setCurrentUser(USER_1); - assertEquals(0, taggingService.getTags(taggedNode).size()); - - nodeService.setProperty(taggedNode, ContentModel.PROP_TITLE, "To ensure we're allowed to write"); - - taggingService.addTag(taggedNode, TAG_1); - taggingService.addTag(taggedNode, TAG_2); - assertEquals(2, taggingService.getTags(taggedNode).size()); - - - // Ensure the folder tag scope got the update - TagScope ts = taggingService.findTagScope(taggedNode); - assertEquals(auditableFolder, ts.getNodeRef()); - assertEquals(0, ts.getTags().size()); - - assertEquals("System", nodeService.getProperty(auditableFolder, ContentModel.PROP_CREATOR)); - assertEquals("System", nodeService.getProperty(ts.getNodeRef(), ContentModel.PROP_MODIFIER)); - - tx = asyncOccurs.awaitExecution(tx); - - ts = taggingService.findTagScope(taggedNode); - assertEquals(auditableFolder, ts.getNodeRef()); - assertEquals(2, ts.getTags().size()); - assertEquals(1, ts.getTag(TAG_1).getCount()); - assertEquals(1, ts.getTag(TAG_2).getCount()); - - // Ensure the auditable flags on the folder are unchanged - assertEquals("System", nodeService.getProperty(auditableFolder, ContentModel.PROP_CREATOR)); - assertEquals("System", nodeService.getProperty(ts.getNodeRef(), ContentModel.PROP_MODIFIER)); - assertEquals(origModified.getTime(), ((Date)nodeService.getProperty(auditableFolder, ContentModel.PROP_MODIFIED)).getTime()); - - // Tidy up - authenticationComponent.setSystemUserAsCurrentUser(); - authenticationService.deleteAuthentication(USER_1); - personService.deletePerson(USER_1); - tx.commit(); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Create a user + if(authenticationService.authenticationExists(USER_1)) + authenticationService.deleteAuthentication(USER_1); + if(personService.personExists(USER_1)) + personService.deletePerson(USER_1); + + authenticationService.createAuthentication(USER_1, "PWD".toCharArray()); + PropertyMap personProperties = new PropertyMap(); + personProperties.put(ContentModel.PROP_USERNAME, USER_1); + personProperties.put(ContentModel.PROP_AUTHORITY_DISPLAY_NAME, "title" + USER_1); + personProperties.put(ContentModel.PROP_FIRSTNAME, "firstName"); + personProperties.put(ContentModel.PROP_LASTNAME, "lastName"); + personProperties.put(ContentModel.PROP_EMAIL, USER_1+"@example.com"); + personProperties.put(ContentModel.PROP_JOBTITLE, "jobTitle"); + personService.createPerson(personProperties); + + + // Give that user permissions on the tagging category root, so + // they're allowed to add new tags + NodeRef tn = taggingService.createTag(folder.getStoreRef(), "Testing"); + NodeRef tr = nodeService.getPrimaryParent(tn).getParentRef(); + permissionService.setPermission(tr, USER_1, PermissionService.EDITOR, true); + permissionService.setPermission(tr, USER_1, PermissionService.CONTRIBUTOR, true); + + + // Create a folder with a tag scope on it + auditable aspect + // User can read but not write + authenticationComponent.setSystemUserAsCurrentUser(); + testData.auditableFolder = nodeService.createNode( + folder, ContentModel.ASSOC_CONTAINS, + QName.createQName("Folder"), ContentModel.TYPE_FOLDER + ).getChildRef(); + nodeService.addAspect(testData.auditableFolder, ContentModel.ASPECT_AUDITABLE, null); + taggingService.addTagScope(testData.auditableFolder); + permissionService.setPermission(testData.auditableFolder, USER_1, PermissionService.CONSUMER, true); + + // Auditable checks + assertEquals("System", nodeService.getProperty(testData.auditableFolder, ContentModel.PROP_CREATOR)); + assertEquals("System", nodeService.getProperty(testData.auditableFolder, ContentModel.PROP_MODIFIER)); + testData.origModified = (Date)nodeService.getProperty(testData.auditableFolder, ContentModel.PROP_MODIFIED); + + + // Create a node without tags, which the user + // can write to + testData.taggedNode = nodeService.createNode( + testData.auditableFolder, ContentModel.ASSOC_CONTAINS, + QName.createQName("Tagged"), ContentModel.TYPE_CONTENT + ).getChildRef(); + permissionService.setPermission(testData.taggedNode, USER_1, PermissionService.EDITOR, true); + + + // Tag the node as the user + authenticationComponent.setCurrentUser(USER_1); + assertEquals(0, taggingService.getTags(testData.taggedNode).size()); + + nodeService.setProperty(testData.taggedNode, ContentModel.PROP_TITLE, "To ensure we're allowed to write"); + + taggingService.addTag(testData.taggedNode, TAG_1); + taggingService.addTag(testData.taggedNode, TAG_2); + assertEquals(2, taggingService.getTags(testData.taggedNode).size()); + + + // Ensure the folder tag scope got the update + TagScope ts = taggingService.findTagScope(testData.taggedNode); + assertEquals(testData.auditableFolder, ts.getNodeRef()); + assertEquals(0, ts.getTags().size()); + + assertEquals("System", nodeService.getProperty(testData.auditableFolder, ContentModel.PROP_CREATOR)); + assertEquals("System", nodeService.getProperty(ts.getNodeRef(), ContentModel.PROP_MODIFIER)); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + TagScope ts = taggingService.findTagScope(testData.taggedNode); + assertEquals(testData.auditableFolder, ts.getNodeRef()); + assertEquals(2, ts.getTags().size()); + assertEquals(1, ts.getTag(TAG_1).getCount()); + assertEquals(1, ts.getTag(TAG_2).getCount()); + + // Ensure the auditable flags on the folder are unchanged + assertEquals("System", nodeService.getProperty(testData.auditableFolder, ContentModel.PROP_CREATOR)); + assertEquals("System", nodeService.getProperty(ts.getNodeRef(), ContentModel.PROP_MODIFIER)); + assertEquals(testData.origModified.getTime(), ((Date)nodeService.getProperty(testData.auditableFolder, ContentModel.PROP_MODIFIED)).getTime()); + + // Tidy up + authenticationComponent.setSystemUserAsCurrentUser(); + authenticationService.deleteAuthentication(USER_1); + personService.deletePerson(USER_1); + return null; + } + }); } // == Test the JavaScript API == public void testJSAPI() throws Exception { - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - Map model = new HashMap(0); - model.put("folder", this.folder); - model.put("subFolder", this.subFolder); - model.put("document", this.document); - model.put("subDocument", this.subDocument); - model.put("tagScopeTest", false); - - ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/tagging/script/test_taggingService.js"); - this.scriptService.executeScript(location, model); - - // Let the script run - tx = asyncOccurs.awaitExecution(tx); - tx.commit(); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + Map model = new HashMap(0); + model.put("folder", folder); + model.put("subFolder", subFolder); + model.put("document", document); + model.put("subDocument", subDocument); + model.put("tagScopeTest", false); + + ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/tagging/script/test_taggingService.js"); + scriptService.executeScript(location, model); + + // Let the script run + return null; + } + }); } public void testJSTagScope() throws Exception { - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); - - // Add a load of tags to test the global tag methods with - this.taggingService.createTag(storeRef, "alpha"); - this.taggingService.createTag(storeRef, "alpha double"); - this.taggingService.createTag(storeRef, "beta"); - this.taggingService.createTag(storeRef, "gamma"); - this.taggingService.createTag(storeRef, "delta"); - - // Add a load of tags and tag scopes to the object and commit before executing the script - this.taggingService.addTagScope(this.folder); - this.taggingService.addTagScope(this.subFolder); - - this.taggingService.addTag(this.subDocument, TAG_1); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subDocument, TAG_2); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subDocument, TAG_3); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subFolder, TAG_1); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.subFolder, TAG_2); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.document, TAG_1); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.document, TAG_2); - tx = asyncOccurs.awaitExecution(tx); - this.taggingService.addTag(this.folder, TAG_1); - tx = asyncOccurs.awaitExecution(tx); - - Map model = new HashMap(0); - model.put("folder", this.folder); - model.put("subFolder", this.subFolder); - model.put("document", this.document); - model.put("subDocument", this.subDocument); - model.put("tagScopeTest", true); - model.put("store", storeRef.toString()); - - ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/tagging/script/test_taggingService.js"); - this.scriptService.executeScript(location, model); - - // Let the script run - tx = asyncOccurs.awaitExecution(tx); - tx.commit(); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Add a load of tags to test the global tag methods with + taggingService.createTag(storeRef, "alpha"); + taggingService.createTag(storeRef, "alpha double"); + taggingService.createTag(storeRef, "beta"); + taggingService.createTag(storeRef, "gamma"); + taggingService.createTag(storeRef, "delta"); + + // Add a load of tags and tag scopes to the object and commit before executing the script + taggingService.addTagScope(folder); + taggingService.addTagScope(subFolder); + + taggingService.addTag(subDocument, TAG_1); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subDocument, TAG_2); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subDocument, TAG_3); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subFolder, TAG_1); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(subFolder, TAG_2); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(document, TAG_1); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(document, TAG_2); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTag(folder, TAG_1); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + Map model = new HashMap(0); + model.put("folder", folder); + model.put("subFolder", subFolder); + model.put("document", document); + model.put("subDocument", subDocument); + model.put("tagScopeTest", true); + model.put("store", storeRef.toString()); + + ScriptLocation location = new ClasspathScriptLocation( + "org/alfresco/repo/tagging/script/test_taggingService.js"); + scriptService.executeScript(location, model); + + // Let the script run + return null; + } + }); } /** @@ -1252,100 +1649,126 @@ public class TaggingServiceImplTest extends TestCase */ public void testOnStartupJob() throws Exception { - UserTransaction tx = this.transactionService.getUserTransaction(); - tx.begin(); + final UpdateTagScopesActionExecuter updateTagsAction = (UpdateTagScopesActionExecuter) ctx + .getBean("update-tagscope"); + class TestData + { + public String lockF; + public String lockSF; + } + final TestData testData = new TestData(); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Nothing is pending to start with + assertEquals(0, updateTagsAction.searchForTagScopesPendingUpdates().size()); + + + // Take the tag scope lock, so that no real updates will happen + testData.lockF = updateTagsAction.lockTagScope(folder); + testData.lockSF = updateTagsAction.lockTagScope(subFolder); + + // Do some tagging + taggingService.addTagScope(folder); + taggingService.addTagScope(subFolder); + + taggingService.addTag(subDocument, TAG_1); + taggingService.addTag(subDocument, TAG_2); + taggingService.addTag(subFolder, TAG_1); + taggingService.addTag(document, TAG_1); + taggingService.addTag(folder, TAG_1); + taggingService.addTag(folder, TAG_3); + return null; + } + }); - // Nothing is pending to start with - UpdateTagScopesActionExecuter updateTagsAction = (UpdateTagScopesActionExecuter) - ctx.getBean("update-tagscope"); - assertEquals(0, updateTagsAction.searchForTagScopesPendingUpdates().size()); - - - // Take the tag scope lock, so that no real updates will happen - String lockF = updateTagsAction.lockTagScope(this.folder); - String lockSF = updateTagsAction.lockTagScope(this.subFolder); - - // Do some tagging - this.taggingService.addTagScope(this.folder); - this.taggingService.addTagScope(this.subFolder); - - this.taggingService.addTag(this.subDocument, TAG_1); - this.taggingService.addTag(this.subDocument, TAG_2); - this.taggingService.addTag(this.subFolder, TAG_1); - this.taggingService.addTag(this.document, TAG_1); - this.taggingService.addTag(this.folder, TAG_1); - this.taggingService.addTag(this.folder, TAG_3); - tx = asyncOccurs.awaitExecution(tx); - - - // Tag scope updates shouldn't have happened yet, - // as the scopes are locked - TagScope ts1 = this.taggingService.findTagScope(this.folder); - TagScope ts2 = this.taggingService.findTagScope(this.subFolder); - assertEquals( - "Wrong tags on folder tagscope: " + ts1.getTags(), - 0, ts1.getTags().size() - ); - assertEquals( - "Wrong tags on folder tagscope: " + ts1.getTags(), - 0, ts2.getTags().size() - ); - - - // Check the pending list now - assertEquals(2, updateTagsAction.searchForTagScopesPendingUpdates().size()); - List pendingScopes = updateTagsAction.searchForTagScopesPendingUpdates(); - assertTrue("Not found in " + pendingScopes, pendingScopes.contains(this.folder)); - assertTrue("Not found in " + pendingScopes, pendingScopes.contains(this.subFolder)); - - - // Have the Quartz bean fire now - // It won't be able to do anything, as the locks are taken - UpdateTagScopesQuartzJob job = new UpdateTagScopesQuartzJob(); - job.execute(actionService, updateTagsAction); - tx = asyncOccurs.awaitExecution(tx); - - // Check that things are still pending despite the quartz run - assertEquals(2, updateTagsAction.searchForTagScopesPendingUpdates().size()); - pendingScopes = updateTagsAction.searchForTagScopesPendingUpdates(); - assertTrue("Not found in " + pendingScopes, pendingScopes.contains(this.folder)); - assertTrue("Not found in " + pendingScopes, pendingScopes.contains(this.subFolder)); - - - // Give back our locks, so we can proceed - updateTagsAction.unlockTagScope(this.folder, lockF); - updateTagsAction.unlockTagScope(this.subFolder, lockSF); - - - // Fire off the quartz bean, this time it can really work - job = new UpdateTagScopesQuartzJob(); - job.execute(actionService, updateTagsAction); - tx = asyncOccurs.awaitExecution(tx); - - - // Now check again - nothing should be pending - assertEquals(0, updateTagsAction.searchForTagScopesPendingUpdates().size()); - - ts1 = this.taggingService.findTagScope(this.folder); - ts2 = this.taggingService.findTagScope(this.subFolder); - assertEquals( - "Wrong tags on folder tagscope: " + ts1.getTags(), - 3, ts1.getTags().size() - ); - assertEquals( - "Wrong tags on folder tagscope: " + ts1.getTags(), - 2, ts2.getTags().size() - ); - - assertEquals(4, ts1.getTag(TAG_1).getCount()); - assertEquals(1, ts1.getTag(TAG_2).getCount()); - assertEquals(1, ts1.getTag(TAG_3.toLowerCase()).getCount()); - - assertEquals(2, ts2.getTag(TAG_1).getCount()); - assertEquals(1, ts2.getTag(TAG_2).getCount()); - - // Force txn commit to prevent test leaks - tx.commit(); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Tag scope updates shouldn't have happened yet, + // as the scopes are locked + TagScope ts1 = taggingService.findTagScope(folder); + TagScope ts2 = taggingService.findTagScope(subFolder); + assertEquals( + "Wrong tags on folder tagscope: " + ts1.getTags(), + 0, ts1.getTags().size() + ); + assertEquals( + "Wrong tags on folder tagscope: " + ts1.getTags(), + 0, ts2.getTags().size() + ); + + + // Check the pending list now + assertEquals(2, updateTagsAction.searchForTagScopesPendingUpdates().size()); + List pendingScopes = updateTagsAction.searchForTagScopesPendingUpdates(); + assertTrue("Not found in " + pendingScopes, pendingScopes.contains(folder)); + assertTrue("Not found in " + pendingScopes, pendingScopes.contains(subFolder)); + + + // Have the Quartz bean fire now + // It won't be able to do anything, as the locks are taken + UpdateTagScopesQuartzJob job = new UpdateTagScopesQuartzJob(); + job.execute(actionService, updateTagsAction); + return null; + } + }); + asyncOccurs.awaitExecution(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Check that things are still pending despite the quartz run + assertEquals(2, updateTagsAction.searchForTagScopesPendingUpdates().size()); + List pendingScopes = updateTagsAction.searchForTagScopesPendingUpdates(); + assertTrue("Not found in " + pendingScopes, pendingScopes.contains(folder)); + assertTrue("Not found in " + pendingScopes, pendingScopes.contains(subFolder)); + return null; + } + }); + + + // Give back our locks, so we can proceed + updateTagsAction.unlockTagScope(folder, testData.lockF); + updateTagsAction.unlockTagScope(subFolder, testData.lockSF); + + + // Fire off the quartz bean, this time it can really work + UpdateTagScopesQuartzJob job = new UpdateTagScopesQuartzJob(); + job.execute(actionService, updateTagsAction); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now check again - nothing should be pending + assertEquals(0, updateTagsAction.searchForTagScopesPendingUpdates().size()); + + TagScope ts1 = taggingService.findTagScope(folder); + TagScope ts2 = taggingService.findTagScope(subFolder); + assertEquals( + "Wrong tags on folder tagscope: " + ts1.getTags(), + 3, ts1.getTags().size() + ); + assertEquals( + "Wrong tags on folder tagscope: " + ts1.getTags(), + 2, ts2.getTags().size() + ); + + assertEquals(4, ts1.getTag(TAG_1).getCount()); + assertEquals(1, ts1.getTag(TAG_2).getCount()); + assertEquals(1, ts1.getTag(TAG_3.toLowerCase()).getCount()); + + assertEquals(2, ts2.getTag(TAG_1).getCount()); + assertEquals(1, ts2.getTag(TAG_2).getCount()); + return null; + } + }); } /** @@ -1354,21 +1777,26 @@ public class TaggingServiceImplTest extends TestCase */ public void DISABLEDtestMultiThreaded() throws Exception { - UserTransaction tx = this.transactionService.getNonPropagatingUserTransaction(); - tx.begin(); - this.taggingService.addTagScope(this.folder); - this.taggingService.addTagScope(this.subFolder); - tx.commit(); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + taggingService.addTagScope(folder); + taggingService.addTagScope(subFolder); + return null; + } + }); // Reset the action count asyncOccurs.wantedActionsCount = 0; // Prepare a bunch of threads to do tagging final List threads = new ArrayList(); - String[] tags = new String[] { TAG_1, TAG_2, TAG_3, TAG_4, TAG_5, - "testTag06", "testTag07", "testTag08", "testTag09", "testTag10", - "testTag11", "testTag12", "testTag13", "testTag14", "testTag15", - "testTag16", "testTag17", "testTag18", "testTag19", "testTag20"}; + final String[] tags = new String[] { TAG_1, TAG_2, TAG_3, TAG_4, TAG_5, + "testTag06", "testTag07", "testTag08", "testTag09", "testTag10", + "testTag11", "testTag12", "testTag13", "testTag14", "testTag15", + "testTag16", "testTag17", "testTag18", "testTag19", "testTag20"}; for (String tmpTag : tags) { final String tag = tmpTag; @@ -1467,30 +1895,34 @@ public class TaggingServiceImplTest extends TestCase System.out.println("Done waiting for tagging, now checking"); // Now check that things ended up as planned - tx = this.transactionService.getUserTransaction(); - tx.begin(); - - TagScope ts1 = this.taggingService.findTagScope(this.folder); - TagScope ts2 = this.taggingService.findTagScope(this.subFolder); - assertEquals( - "Wrong tags on folder tagscope: " + ts1.getTags(), - tags.length, ts1.getTags().size() - ); - assertEquals( - "Wrong tags on subfolder tagscope: " + ts2.getTags(), - tags.length, ts2.getTags().size() - ); - - // Each tag should crop up 3 times on the folder - // and twice for the subfolder - for (String tag : tags) + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - assertEquals(3, ts1.getTag(tag.toLowerCase()).getCount()); - assertEquals(2, ts2.getTag(tag.toLowerCase()).getCount()); - } - - // All done - tx.commit(); + @Override + public Void execute() throws Throwable + { + TagScope ts1 = taggingService.findTagScope(folder); + TagScope ts2 = taggingService.findTagScope(subFolder); + assertEquals( + "Wrong tags on folder tagscope: " + ts1.getTags(), + tags.length, ts1.getTags().size() + ); + assertEquals( + "Wrong tags on subfolder tagscope: " + ts2.getTags(), + tags.length, ts2.getTags().size() + ); + + // Each tag should crop up 3 times on the folder + // and twice for the subfolder + for (String tag : tags) + { + assertEquals(3, ts1.getTag(tag.toLowerCase()).getCount()); + assertEquals(2, ts2.getTag(tag.toLowerCase()).getCount()); + } + + // All done + return null; + } + }); } @@ -1519,15 +1951,14 @@ public class TaggingServiceImplTest extends TestCase } } - public UserTransaction awaitExecution(UserTransaction tx) throws Exception + public T awaitExecution(RetryingTransactionCallback callback) throws Exception { + T returnVal = transactionService.getRetryingTransactionHelper().doInTransaction(callback); + synchronized (waitForExecutionLock) { - // Have things begin working - if(tx != null) - tx.commit(); - // Always wait 25ms - waitForExecutionLock.wait(25); + // Always wait 100ms + waitForExecutionLock.wait(100); // If there are actions if the required type // currently running, keep waiting longer for @@ -1557,10 +1988,7 @@ public class TaggingServiceImplTest extends TestCase } catch(InterruptedException e) {} } - // Now create a new transaction for them - tx = transactionService.getUserTransaction(); - tx.begin(); - return tx; + return returnVal; } } } diff --git a/source/java/org/alfresco/repo/template/ISO8601DateFormatMethod.java b/source/java/org/alfresco/repo/template/ISO8601DateFormatMethod.java index 8d38bf8a44..34f36ca765 100644 --- a/source/java/org/alfresco/repo/template/ISO8601DateFormatMethod.java +++ b/source/java/org/alfresco/repo/template/ISO8601DateFormatMethod.java @@ -25,15 +25,19 @@ import org.springframework.extensions.surf.util.ISO8601DateFormat; import freemarker.template.TemplateDateModel; import freemarker.template.TemplateMethodModelEx; import freemarker.template.TemplateModelException; +import freemarker.template.TemplateScalarModel; /** * @author David Caruana + * @author Kevin Roast * * Custom FreeMarker Template language method. *

    - * Render Date to ISO8601 format. + * Render Date to ISO8601 format.
    + * Or parse ISO6801 format string date to a Date object. *

    * Usage: xmldate(Date date) + * xmldate(String date) */ public class ISO8601DateFormatMethod extends BaseTemplateProcessorExtension implements TemplateMethodModelEx { @@ -42,7 +46,7 @@ public class ISO8601DateFormatMethod extends BaseTemplateProcessorExtension impl */ public Object exec(List args) throws TemplateModelException { - String result = ""; + Object result = null; if (args.size() == 1) { @@ -51,8 +55,12 @@ public class ISO8601DateFormatMethod extends BaseTemplateProcessorExtension impl { result = ISO8601DateFormat.format(((TemplateDateModel)arg0).getAsDate()); } + else if (arg0 instanceof TemplateScalarModel) + { + result = ISO8601DateFormat.parse(((TemplateScalarModel)arg0).getAsString()); + } } - return result; + return result != null ? result : ""; } -} +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/template/TemplateNode.java b/source/java/org/alfresco/repo/template/TemplateNode.java index b77c098416..77cd1ea346 100644 --- a/source/java/org/alfresco/repo/template/TemplateNode.java +++ b/source/java/org/alfresco/repo/template/TemplateNode.java @@ -28,6 +28,8 @@ import java.util.Map; import java.util.Set; import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.admin.SysAdminParamsImpl; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.lock.LockStatus; @@ -36,6 +38,7 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.namespace.NamespacePrefixResolver; @@ -43,6 +46,7 @@ import org.alfresco.service.namespace.NamespacePrefixResolverProvider; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QNameMap; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.UrlUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.InputSource; @@ -556,6 +560,39 @@ public class TemplateNode extends BasePermissionsNode implements NamespacePrefix return qname; } + /** + * This method returns a URL string which resolves to an Alfresco Share view of this node. + * Note that in order for this method to return meaningful data, the {@link SysAdminParams sysAdminParams} + * bean must have been configured. + *

    + * Currently this method only produces valid URls for documents and not for folders. + * @see SysAdminParamsImpl#setAlfrescoHost(String) + * @see SysAdminParamsImpl#setShareHost(String) + */ + public String getShareUrl() + { + // TODO URLs for the repo server. + // TODO URLs for folders + + SiteInfo siteInfo = services.getSiteService().getSite(getNodeRef()); + String siteShortName = siteInfo == null ? null : siteInfo.getShortName(); + + String baseUrl = UrlUtil.getShareUrl(services.getSysAdminParams()); + + StringBuilder result = new StringBuilder(); + result.append(baseUrl) + .append("/page/"); + if (siteShortName != null) + { + result.append("site/").append(siteShortName).append("/"); + } + + result.append("document-details?nodeRef=") + .append(getNodeRef()); + + return result.toString(); + } + // ------------------------------------------------------------------------------ // Inner classes diff --git a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java index c402d3e584..c55b2c243b 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java @@ -397,12 +397,12 @@ public class MultiTServiceImpl implements TenantService if ((tenantUserDomain == null) || (! tenantDomain.equals(tenantUserDomain))) { - throw new AlfrescoRuntimeException("domain mismatch: expected = " + tenantDomain + ", actual = " + tenantUserDomain); + throw new TenantDomainMismatchException(tenantDomain, tenantUserDomain); } } else { - throw new AlfrescoRuntimeException("domain mismatch: expected = " + tenantDomain + ", actual = "); + throw new TenantDomainMismatchException(tenantDomain, null); } } } diff --git a/source/java/org/alfresco/repo/tenant/TenantDomainMismatchException.java b/source/java/org/alfresco/repo/tenant/TenantDomainMismatchException.java new file mode 100644 index 0000000000..3459039e26 --- /dev/null +++ b/source/java/org/alfresco/repo/tenant/TenantDomainMismatchException.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.tenant; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Exception thrown when an operation that requires + * two things to be in the same tenant domain discovers + * that they belong to different ones. + */ +@SuppressWarnings("serial") +public class TenantDomainMismatchException extends AlfrescoRuntimeException +{ + private String tenantA; + private String tenantB; + + public TenantDomainMismatchException(String tenantA, String tenantB) + { + super( + "domain mismatch: expected = " + renderTenent(tenantA) + + ", actual = " + renderTenent(tenantB) + ); + + this.tenantA = tenantA; + this.tenantB = tenantB; + } + private static String renderTenent(String tenant) + { + if(tenant == null) + return ""; + return tenant; + } + + public String getTenantA() + { + return tenantA; + } + public String getTenantB() + { + return tenantB; + } +} diff --git a/source/java/org/alfresco/repo/thumbnail/AddFailedThumbnailActionExecuter.java b/source/java/org/alfresco/repo/thumbnail/AddFailedThumbnailActionExecuter.java new file mode 100644 index 0000000000..19292b0778 --- /dev/null +++ b/source/java/org/alfresco/repo/thumbnail/AddFailedThumbnailActionExecuter.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.repo.thumbnail; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.executer.ActionExecuter; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.thumbnail.conditions.NodeEligibleForRethumbnailingEvaluator; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +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.thumbnail.FailedThumbnailInfo; +import org.alfresco.service.cmr.thumbnail.ThumbnailService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This {@link ActionExecuter} implementation is used to record a failed thumbnail attempt + * having occurred on a source node. For the specified {@link ThumbnailDefinition#getName() thumbnail definition name} + * and the specified failure datetime, this action executer applies the {@link ContentModel#ASPECT_FAILED_THUMBNAIL_SOURCE + * cm:failedThumbnailSource} aspect and creates a {@link ContentModel#TYPE_FAILED_THUMBNAIL cm:failedThumbnail} child to store + * the failure data. + *

    + * Some pieces of content cannot be thumbnailed. This can happen for various reasons, e.g. + *

      + *
    • there is something in the content itself which is challenging, complex or not compliant with the relevant spec.
    • + *
    • there is something missing from the relevant library/ies which are trying to produce the thumbnail.
    • + *
    + * Some content can take a not insignificant amount of time in producing the thumbnail - only to fail. + * This cost is borne each time a create-thumbnail action is run on that content, which happens each + * time a user looks at the doclib page for that content in Share. For problematic documents that take a long time + * to fail, this can add up to a significant cpu cost on the repository server. + * Therefore we limit the frequency with which the repository retries to create thumbnails. + *

    + * The details of how these thumbnail creations are limited is described in {@link NodeEligibleForRethumbnailingEvaluator}. + + * + * @author Neil Mc Erlean + * @since 3.5.0 + * + * @see FailedThumbnailInfo + * @see NodeEligibleForRethumbnailingEvaluator + * @see ThumbnailServiceImpl#init() + */ +public class AddFailedThumbnailActionExecuter extends ActionExecuterAbstractBase +{ + private static Log log = LogFactory.getLog(AddFailedThumbnailActionExecuter.class); + + /** + * The action bean name. + */ + public static final String NAME = "add-failed-thumbnail"; + + /** + * The name of the failed thumbnail definition e.g. doclib. + */ + public static final String PARAM_THUMBNAIL_DEFINITION_NAME = "thumbnail-definition-name"; + + /** + * The parameter defines the failure datetime to be recorded against the source node. + * We explicitly require a parameterised value for this (rather than simply using 'now') + * because this action is executed asynchronously and there is the possibility that the time + * of action execution is later than the actual failure time. + */ + public static final String PARAM_FAILURE_DATETIME = "failure-datetime"; + + /** + * The node service + */ + private NodeService nodeService; + + /** + * Thumbnail Service + */ + ThumbnailService thumbnailService; + + /** + * The behaviour filter. + */ + private BehaviourFilter behaviourFilter; + + /** + * Set the node service + * + * @param nodeService the node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the thumbnail service + * + * @param thumbnailService the thumbnail service + */ + public void setThumbnailService(ThumbnailService thumbnailService) + { + this.thumbnailService = thumbnailService; + } + + /** + * Set the behaviour filter. + */ + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + /** + * @see org.alfresco.repo.action.executer.ActionExecuter#execute(org.alfresco.service.cmr.repository.NodeRef, NodeRef) + */ + public void executeImpl(Action ruleAction, NodeRef actionedUponNodeRef) + { + final boolean nodeExists = this.nodeService.exists(actionedUponNodeRef); + if (nodeExists) + { + Map paramValues = ruleAction.getParameterValues(); + final String thumbDefName = (String)paramValues.get(PARAM_THUMBNAIL_DEFINITION_NAME); + final Date failureDateTime = (Date)paramValues.get(PARAM_FAILURE_DATETIME); + + final QName thumbDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, thumbDefName); + + NodeRef existingThumbnail = thumbnailService.getThumbnailByName(actionedUponNodeRef, + ContentModel.PROP_CONTENT_PROPERTY_NAME, thumbDefName); + + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Adding ").append(ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE) + .append(" to ").append(actionedUponNodeRef); + log.debug(msg.toString()); + + msg = new StringBuilder(); + msg.append(" failed thumbnail definition is ").append(thumbDefName); + log.debug(msg.toString()); + + msg = new StringBuilder(); + msg.append(" failed datetime is ").append(failureDateTime); + log.debug(msg.toString()); + + msg = new StringBuilder(); + msg.append(" existing thumbnail is ").append(existingThumbnail); + log.debug(msg.toString()); + } + + if (nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE) == false) + { + behaviourFilter.disableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); + try + { + this.nodeService.addAspect(actionedUponNodeRef, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE, null); + } + finally + { + behaviourFilter.enableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); + } + } + + List failedChildren = nodeService.getChildAssocs(actionedUponNodeRef, ContentModel.ASSOC_FAILED_THUMBNAIL, thumbDefQName); + NodeRef childNode = failedChildren.isEmpty() ? null : failedChildren.get(0).getChildRef(); + + // Does the actionedUponNodeRef already have a child for this thumbnail definition? + if (childNode == null) + { + // No existing failedThumbnail child, so this is a first time failure to render this source node with the current + // thumbnail definition. + // We'll create a new failedThumbnail child under the source node. + Map props = new HashMap(); + props.put(ContentModel.PROP_FAILED_THUMBNAIL_TIME, failureDateTime); + props.put(ContentModel.PROP_FAILURE_COUNT, 1); + + behaviourFilter.disableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); + try + { + // The association is named after the failed thumbnail definition. + nodeService.createNode(actionedUponNodeRef, ContentModel.ASSOC_FAILED_THUMBNAIL, + thumbDefQName, ContentModel.TYPE_FAILED_THUMBNAIL, props); + } + finally + { + behaviourFilter.enableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); + } + } + else + { + // There is already an existing failedThumbnail child, so this is a repeat failure to perform the same + // thumbnail definition. + // Therefore we don't need to create a new failedThumbnail child. + // But we do need to update the failedThumbnailTime property. + nodeService.setProperty(childNode, ContentModel.PROP_FAILED_THUMBNAIL_TIME, failureDateTime); + + // and increment the failure count. + int currentFailureCount = (Integer) nodeService.getProperty(childNode, ContentModel.PROP_FAILURE_COUNT); + nodeService.setProperty(childNode, ContentModel.PROP_FAILURE_COUNT, currentFailureCount + 1); + } + } + } + + /** + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_THUMBNAIL_DEFINITION_NAME, DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_THUMBNAIL_DEFINITION_NAME), false)); + paramList.add(new ParameterDefinitionImpl(PARAM_FAILURE_DATETIME, DataTypeDefinition.DATETIME, true, getParamDisplayLabel(PARAM_FAILURE_DATETIME), false)); + } +} diff --git a/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java b/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java index 14932438d1..88f94ec2c1 100644 --- a/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java +++ b/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java @@ -125,7 +125,12 @@ public class CreateThumbnailActionExecuter extends ActionExecuterAbstractBase } catch (Exception exception) { - logger.info("Creation of thumbnail '" + details.getName() + "' failed"); + final String msg = "Creation of thumbnail '" + details.getName() + "' failed"; + logger.info(msg); + + // We need to rethrow in order to trigger the compensating action. + // See AddFailedThumbnailActionExecuter + throw new AlfrescoRuntimeException(msg, exception); } } } diff --git a/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java b/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java new file mode 100644 index 0000000000..47d4ba4820 --- /dev/null +++ b/source/java/org/alfresco/repo/thumbnail/FailedThumbnailSourceAspect.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.repo.thumbnail; + +import java.io.Serializable; +import java.util.Map; +import java.util.Map.Entry; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.node.NodeServicePolicies.OnDeleteNodePolicy; +import org.alfresco.repo.policy.Behaviour; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.thumbnail.FailedThumbnailInfo; +import org.alfresco.service.cmr.thumbnail.ThumbnailService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Behaviour/Policies for the {@link ContentModel#ASPECT_FAILED_THUMBNAIL_SOURCE} aspect. + * When the last {@link ContentModel#TYPE_FAILED_THUMBNAIL} child is deleted from under + * a source node, then all failures are considered removed and the {@link ContentModel#ASPECT_FAILED_THUMBNAIL_SOURCE} + * aspect can be removed. + *

    + * Also, any {@link ContentModel#TYPE_FAILED_THUMBNAIL failed thumbnails} should be + * removed from the model onUpdateProperties as the new content may have become thumbnailable. + + * @author Neil Mc Erlean + * @since 3.5.0 + */ +public class FailedThumbnailSourceAspect implements NodeServicePolicies.OnDeleteNodePolicy, + NodeServicePolicies.OnUpdatePropertiesPolicy +{ + private static final Log log = LogFactory.getLog(FailedThumbnailSourceAspect.class); + + /** Services */ + private BehaviourFilter behaviourFilter; + private NodeService nodeService; + private PolicyComponent policyComponent; + private ThumbnailService thumbnailService; + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setThumbnailService(ThumbnailService thumbnailService) + { + this.thumbnailService = thumbnailService; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + /** + * Initialise method + */ + public void init() + { + this.policyComponent.bindClassBehaviour( + OnDeleteNodePolicy.QNAME, + ContentModel.TYPE_FAILED_THUMBNAIL, + new JavaBehaviour(this, "onDeleteNode", Behaviour.NotificationFrequency.EVERY_EVENT)); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), + ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE, + new JavaBehaviour(this, "onUpdateProperties", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); + } + + @Override + public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isNodeArchived) + { + // When a failedThumbnail node has been deleted, we should check if there are any other + // failedThumbnail peer nodes left. + // If there are not, then we can remove the failedThumbnailSource aspect. + + Map failures = thumbnailService.getFailedThumbnails(childAssocRef.getParentRef()); + + if (failures.isEmpty()) + { + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("No remaining failedThumbnail children of ") + .append(childAssocRef.getParentRef()) + .append(" therefore removing aspect ").append(ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE); + log.debug(msg.toString()); + } + behaviourFilter.disableBehaviour(childAssocRef.getParentRef(), ContentModel.ASPECT_AUDITABLE); + try + { + nodeService.removeAspect(childAssocRef.getParentRef(), ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE); + } + finally + { + behaviourFilter.enableBehaviour(childAssocRef.getParentRef(), ContentModel.ASPECT_AUDITABLE); + } + } + } + + @Override + public void onUpdateProperties( + NodeRef nodeRef, + Map before, + Map after) + { + if (this.nodeService.exists(nodeRef)) + { + deleteFailedThumbnailChildren(nodeRef); + } + } + + /** + * Delete all cm:failedThumbnail children as they represent a failure to thumbnail + * the old content. By deleting all cm:failedThumbnail children, the cm:failedThumbnailSource + * aspect will be automatically removed by a policy/behaviour in the ThumbnailService. + * + * This is necessary so that if a new version of a 'broken' document is uploaded, then + * it will be thumbnailed in the normal way. + */ + private void deleteFailedThumbnailChildren(NodeRef nodeRef) + { + Map failedThumbnails = thumbnailService.getFailedThumbnails(nodeRef); + + behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + try + { + if (log.isDebugEnabled()) + { + log.debug("Deleting " + failedThumbnails.size() + " " + ContentModel.TYPE_FAILED_THUMBNAIL + " nodes"); + } + for (Entry entry : failedThumbnails.entrySet()) + { + FailedThumbnailInfo info = entry.getValue(); + nodeService.deleteNode(info.getFailedThumbnailNode()); + } + } + finally + { + behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + } + } +} diff --git a/source/java/org/alfresco/repo/thumbnail/FailureHandlingOptions.java b/source/java/org/alfresco/repo/thumbnail/FailureHandlingOptions.java new file mode 100644 index 0000000000..653dc93271 --- /dev/null +++ b/source/java/org/alfresco/repo/thumbnail/FailureHandlingOptions.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.repo.thumbnail; + +import org.alfresco.repo.thumbnail.conditions.NodeEligibleForRethumbnailingEvaluator; + +/** + * This class holds failure-related configuration data for a {@link ThumbnailDefinition}. + *

    + * Note that a failed thumbnail creation is not the same as a creation which was not attempted for lack of transformers. + * + * @author Neil Mc Erlean + * @since 3.5.0 + * @see NodeEligibleForRethumbnailingEvaluator for a description of how these configuration parameters are used. + */ +public class FailureHandlingOptions +{ + public static final int DEFAULT_PERIOD = 0; + public static final int DEFAULT_RETRY_COUNT = 2; + public static final boolean DEFAULT_QUIET_PERIOD_RETRIES_ENABLED = true; + + /** + * The minimum amount of time (in seconds) before a 'difficult' piece of content should be reattempted. + */ + private long quietPeriod = DEFAULT_PERIOD; + + /** + * The minimum amount of time (in seconds) before a {@link ThumbnailDefinition} should be initially reattempted. + */ + private long retryPeriod = DEFAULT_PERIOD; + + /** + * The maximum number of times to try to thumbnail a normal piece of content. + */ + private int retryCount = DEFAULT_RETRY_COUNT; + + /** + * Are thumbnail retries enabled for difficult content? + */ + private boolean quietPeriodRetriesEnabled = DEFAULT_QUIET_PERIOD_RETRIES_ENABLED; + + public int getRetryCount() + { + return retryCount; + } + + public void setRetryCount(int retryCount) + { + this.retryCount = retryCount; + } + + public boolean getQuietPeriodRetriesEnabled() + { + return quietPeriodRetriesEnabled; + } + + public void setQuietPeriodRetriesEnabled(boolean quietPeriodRetriesEnabled) + { + this.quietPeriodRetriesEnabled = quietPeriodRetriesEnabled; + } + + /** + * Sets the initial minimum retry period for thumbnail creation/update. + * @param initialMinimumRetryPeriod minimum retry period in ms. + */ + public void setRetryPeriod(long retryPeriod) + { + this.retryPeriod = retryPeriod; + } + + public long getRetryPeriod() + { + return this.retryPeriod; + } + + /** + * Sets the minimum retry period for thumbnail creation/update. + * @param minimumRetryPeriodLong minimum retry period in ms. + */ + public void setQuietPeriod(long quietPeriod) + { + this.quietPeriod = quietPeriod; + } + + public long getQuietPeriod() + { + return this.quietPeriod; + } +} diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java index 9f45220f97..597a77c8a3 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -36,9 +36,17 @@ public class ThumbnailDefinition /** Transformation options */ private TransformationOptions options; - /** Path to placeholder thumbnail */ + /** Failure options */ + private FailureHandlingOptions failureOptions; + + /** Path to placeholder thumbnail + */ private String placeHolderResourcePath; + /** Path to mime aware placeholder thumbnail + * */ + private String mimeAwarePlaceHolderResourcePath; + /** Username to run the thumbnailrendition as */ private String runAs; @@ -115,7 +123,7 @@ public class ThumbnailDefinition { this.options = options; } - + /** * Get the transformation options * @@ -125,6 +133,28 @@ public class ThumbnailDefinition { return options; } + + /** + * Set the {@link FailureHandlingOptions failure options}. + * + * @param failureOptions the failure options. + * @since 3.5.0 + */ + public void setFailureHandlingOptions(FailureHandlingOptions failureOptions) + { + this.failureOptions = failureOptions; + } + + /** + * Get the {@link FailureHandlingOptions failure options}. + * + * @return the failure options + * @since 3.5.0 + */ + public FailureHandlingOptions getFailureHandlingOptions() + { + return failureOptions; + } /** * Sets the name of the thumbnail @@ -157,6 +187,7 @@ public class ThumbnailDefinition } /** + * This method sets the placeholder resource path. * * @param placeHolderResourcePath */ @@ -173,4 +204,25 @@ public class ThumbnailDefinition { return placeHolderResourcePath; } + + /** + * This method sets the mime-aware placeholder resource path template. + * + * @param mimeAwarePlaceHolderResourcePath + * @since 3.4.1 (Team) + */ + public void setMimeAwarePlaceHolderResourcePath(String mimeAwarePlaceHolderResourcePath) + { + this.mimeAwarePlaceHolderResourcePath = mimeAwarePlaceHolderResourcePath; + } + + /** + * + * @return + * @since 3.4.1 (Team) + */ + public String getMimeAwarePlaceHolderResourcePath() + { + return mimeAwarePlaceHolderResourcePath; + } } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailHelper.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailHelper.java new file mode 100644 index 0000000000..8ded1568ff --- /dev/null +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailHelper.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.repo.thumbnail; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.repo.thumbnail.conditions.NodeEligibleForRethumbnailingEvaluator; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionService; + +/** + * A simple helper class for constructing create-thumbnail actions decorated with the correct + * condition and compensating actions. + * + * @author Neil Mc Erlean + * @since 3.5.0 + */ +public class ThumbnailHelper +{ + public static Action createCreateThumbnailAction(ThumbnailDefinition thumbnailDef, ServiceRegistry services) + { + ActionService actionService = services.getActionService(); + + Action action = actionService.createAction(CreateThumbnailActionExecuter.NAME); + action.setParameterValue(CreateThumbnailActionExecuter.PARAM_THUMBANIL_NAME, thumbnailDef.getName()); + + decorateAction(thumbnailDef, action, actionService); + + return action; + } + + + private static void decorateAction(ThumbnailDefinition thumbnailDef, Action action, ActionService actionService) + { + final FailureHandlingOptions failureOptions = thumbnailDef.getFailureHandlingOptions(); + long retryPeriod = failureOptions == null ? FailureHandlingOptions.DEFAULT_PERIOD : failureOptions.getRetryPeriod() * 1000l; + int retryCount = failureOptions == null ? FailureHandlingOptions.DEFAULT_RETRY_COUNT : failureOptions.getRetryCount(); + long quietPeriod = failureOptions == null ? FailureHandlingOptions.DEFAULT_PERIOD : failureOptions.getQuietPeriod() * 1000l; + boolean quietPeriodRetriesEnabled = failureOptions == null ? + FailureHandlingOptions.DEFAULT_QUIET_PERIOD_RETRIES_ENABLED : failureOptions.getQuietPeriodRetriesEnabled(); + + // The thumbnail/action should only be run if it is eligible. + Map failedThumbnailConditionParams = new HashMap(); + failedThumbnailConditionParams.put(NodeEligibleForRethumbnailingEvaluator.PARAM_THUMBNAIL_DEFINITION_NAME, thumbnailDef.getName()); + failedThumbnailConditionParams.put(NodeEligibleForRethumbnailingEvaluator.PARAM_RETRY_PERIOD, retryPeriod); + failedThumbnailConditionParams.put(NodeEligibleForRethumbnailingEvaluator.PARAM_RETRY_COUNT, retryCount); + failedThumbnailConditionParams.put(NodeEligibleForRethumbnailingEvaluator.PARAM_QUIET_PERIOD, quietPeriod); + failedThumbnailConditionParams.put(NodeEligibleForRethumbnailingEvaluator.PARAM_QUIET_PERIOD_RETRIES_ENABLED, quietPeriodRetriesEnabled); + + ActionCondition thumbnailCondition = actionService.createActionCondition(NodeEligibleForRethumbnailingEvaluator.NAME, failedThumbnailConditionParams); + + + // If it is run and if it fails, then we want a compensating action to run which will mark + // the source node as having failed to produce a thumbnail. + Action applyBrokenThumbnail = actionService.createAction("add-failed-thumbnail"); + applyBrokenThumbnail.setParameterValue(AddFailedThumbnailActionExecuter.PARAM_THUMBNAIL_DEFINITION_NAME, thumbnailDef.getName()); + applyBrokenThumbnail.setParameterValue(AddFailedThumbnailActionExecuter.PARAM_FAILURE_DATETIME, new Date()); + + action.addActionCondition(thumbnailCondition); + action.setCompensatingAction(applyBrokenThumbnail); + } +} diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java index dff4d606e3..6a2abceb7f 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java @@ -26,6 +26,7 @@ import java.util.Map; import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.rendition.RenditionDefinition; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ContentService; @@ -145,25 +146,38 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi } return; } - - AuthenticationUtil.runAs(new RunAsWork() { - public Void doWork() throws Exception + + // Otherwise we should go ahead and persist the thumbnail definitions. + // This is done during system startup. It needs to be done as the system user to ensure the thumbnail definitions get saved + // and also needs to be done within a transaction in order to support concurrent startup. See ALF-6271 for details. + RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper(); + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable { - for (String thumbnailDefName : thumbnailDefinitions.keySet()) - { - final ThumbnailDefinition thumbnailDefinition = thumbnailDefinitions.get(thumbnailDefName); - - // Built-in thumbnailDefinitions do not provide any non-standard values - // for the ThumbnailParentAssociationDetails object. Hence the null - RenditionDefinition renditionDef = thumbnailRenditionConvertor.convert(thumbnailDefinition, null); - - // Thumbnail definitions are saved into the repository as actions - renditionService.saveRenditionDefinition(renditionDef); - } - + AuthenticationUtil.runAs(new RunAsWork() { + public Void doWork() throws Exception + { + for (String thumbnailDefName : thumbnailDefinitions.keySet()) + { + final ThumbnailDefinition thumbnailDefinition = thumbnailDefinitions.get(thumbnailDefName); + + // Built-in thumbnailDefinitions do not provide any non-standard values + // for the ThumbnailParentAssociationDetails object. Hence the null + RenditionDefinition renditionDef = thumbnailRenditionConvertor.convert(thumbnailDefinition, null); + + // Thumbnail definitions are saved into the repository as actions + renditionService.saveRenditionDefinition(renditionDef); + } + + return null; + } + }, AuthenticationUtil.getSystemUserName()); + return null; } - }, AuthenticationUtil.getSystemUserName()); + }); } /** diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java index d4c653f409..823c1a5e69 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -20,13 +20,22 @@ package org.alfresco.repo.thumbnail; import java.io.Serializable; import java.util.ArrayList; +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 org.alfresco.model.ContentModel; import org.alfresco.repo.content.transform.RuntimeExecutableContentTransformerOptions; import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; import org.alfresco.repo.content.transform.swf.SWFTransformationOptions; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.Behaviour; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; import org.alfresco.repo.rendition.executer.ImageRenderingEngine; import org.alfresco.repo.rendition.executer.ReformatRenderingEngine; @@ -39,11 +48,13 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.TransformationOptions; +import org.alfresco.service.cmr.thumbnail.FailedThumbnailInfo; import org.alfresco.service.cmr.thumbnail.ThumbnailException; import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails; import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,7 +64,9 @@ import org.springframework.extensions.surf.util.ParameterCheck; * @author Roy Wetherall * @author Neil McErlean */ -public class ThumbnailServiceImpl implements ThumbnailService +public class ThumbnailServiceImpl implements ThumbnailService, + NodeServicePolicies.BeforeCreateNodePolicy, + NodeServicePolicies.OnCreateNodePolicy { /** Logger */ private static Log logger = LogFactory.getLog(ThumbnailServiceImpl.class); @@ -75,6 +88,13 @@ public class ThumbnailServiceImpl implements ThumbnailService /** Rendition service */ private RenditionService renditionService; + /** + * The policy component. + * @since 3.5.0 + */ + private PolicyComponent policyComponent; + + /** * Set the rendition service. * @@ -105,6 +125,87 @@ public class ThumbnailServiceImpl implements ThumbnailService this.thumbnailRegistry = thumbnailRegistry; } + /** + * Set the policy component to listen for various events + * @since 3.5.0 + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * Registers to listen for events of interest. + * @since 3.5.0 + */ + public void init() + { + policyComponent.bindClassBehaviour( + NodeServicePolicies.OnCreateNodePolicy.QNAME, + ContentModel.TYPE_THUMBNAIL, + new JavaBehaviour(this, "onCreateNode", Behaviour.NotificationFrequency.EVERY_EVENT)); + policyComponent.bindClassBehaviour( + NodeServicePolicies.BeforeCreateNodePolicy.QNAME, + ContentModel.TYPE_FAILED_THUMBNAIL, + new JavaBehaviour(this, "beforeCreateNode", Behaviour.NotificationFrequency.EVERY_EVENT)); + } + + public void beforeCreateNode( + NodeRef parentRef, + QName assocTypeQName, + QName assocQName, + QName nodeTypeQName) + { + // When a thumbnail has failed, we must delete any existing (successful) thumbnails of that thumbnailDefinition. + if (ContentModel.TYPE_FAILED_THUMBNAIL.equals(nodeTypeQName)) + { + // In fact there should only be zero or one such thumbnails + Set childNodeTypes = new HashSet(); + childNodeTypes.add(ContentModel.TYPE_THUMBNAIL); + List existingThumbnails = nodeService.getChildAssocs(parentRef, childNodeTypes); + + for (ChildAssociationRef chAssRef : existingThumbnails) + { + if (chAssRef.getQName().equals(assocQName)) + { + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Deleting thumbnail node ").append(chAssRef.getChildRef()); + logger.debug(msg.toString()); + } + nodeService.deleteNode(chAssRef.getChildRef()); + } + } + } + + // We can't respond to the creation of a cm:thumbnail node at this point as they are created with + // temporary assoc qnames and so cannot be matched to the relevant thumbnail definition. + // Instead we must do it "onCreateNode()" + } + + public void onCreateNode(ChildAssociationRef childAssoc) + { + // When a thumbnail succeeds, we must delete any existing thumbnail failure nodes. + String thumbnailName = (String) nodeService.getProperty(childAssoc.getChildRef(), ContentModel.PROP_NAME); + + // In fact there should only be zero or one such failedThumbnails + Map failures = getFailedThumbnails(childAssoc.getParentRef()); + FailedThumbnailInfo existingFailedThumbnail = failures.get(thumbnailName); + + if (existingFailedThumbnail != null) + { + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Deleting failedThumbnail node ").append(existingFailedThumbnail.getFailedThumbnailNode()); + logger.debug(msg.toString()); + } + nodeService.deleteNode(existingFailedThumbnail.getFailedThumbnailNode()); + } + } + + /** * @see org.alfresco.service.cmr.thumbnail.ThumbnailService#getThumbnailRegistry() */ @@ -223,7 +324,6 @@ public class ThumbnailServiceImpl implements ThumbnailService */ public void updateThumbnail(final NodeRef thumbnail, final TransformationOptions transformationOptions) { - // Seeing as how this always gives its own options, maybe this should be a delete and recreate? if (logger.isDebugEnabled() == true) { logger.debug("Updating thumbnail (thumbnail=" + thumbnail.toString() + ")"); @@ -255,8 +355,17 @@ public class ThumbnailServiceImpl implements ThumbnailService transformationOptions.setSourceContentProperty(contentProperty); transformationOptions.setTargetContentProperty(ContentModel.PROP_CONTENT); - // Do the thumbnail transformation - RenditionDefinition rendDefn = renditionService.loadRenditionDefinition(renditionAssociationName); + // Do the thumbnail transformation. Rendition Definitions are persisted underneath the Data Dictionary for which Group ALL + // has Consumer access by default. However, we cannot assume that that access level applies for all deployments. See ALF-7334. + RenditionDefinition rendDefn = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + @Override + public RenditionDefinition doWork() throws Exception + { + return renditionService.loadRenditionDefinition(renditionAssociationName); + } + }, AuthenticationUtil.getSystemUserName()); + if (rendDefn == null) { String renderingEngineName = getRenderingEngineNameFor(transformationOptions); @@ -355,6 +464,35 @@ public class ThumbnailServiceImpl implements ThumbnailService return thumbnails; } + @Override + public Map getFailedThumbnails(NodeRef sourceNode) + { + Map result = Collections.emptyMap(); + + if (nodeService.hasAspect(sourceNode, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE)) + { + List failedThumbnailChildren = nodeService.getChildAssocs(sourceNode, + ContentModel.ASSOC_FAILED_THUMBNAIL, RegexQNamePattern.MATCH_ALL); + result = new HashMap(); + for (ChildAssociationRef chAssRef : failedThumbnailChildren) + { + final QName failedThumbnailName = chAssRef.getQName(); + NodeRef failedThumbnailNode = chAssRef.getChildRef(); + Map props = nodeService.getProperties(failedThumbnailNode); + Date failureDateTime = (Date)props.get(ContentModel.PROP_FAILED_THUMBNAIL_TIME); + int failureCount = (Integer)props.get(ContentModel.PROP_FAILURE_COUNT); + + final FailedThumbnailInfo failedThumbnailInfo = new FailedThumbnailInfo(failedThumbnailName.getLocalName(), + failureDateTime, failureCount, + failedThumbnailNode); + result.put(failedThumbnailName.getLocalName(), failedThumbnailInfo); + } + } + + return result; + } + + /** * Determine whether the thumbnail meta-data matches the given mimetype and options * diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java index 5b150e8145..03c3a2480c 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -22,6 +22,7 @@ package org.alfresco.repo.thumbnail; import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -34,15 +35,21 @@ import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.repo.content.transform.magick.ImageResizeOptions; import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; import org.alfresco.repo.jscript.ClasspathScriptLocation; +import org.alfresco.repo.thumbnail.script.ScriptThumbnailService; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.ScriptLocation; import org.alfresco.service.cmr.repository.ScriptService; +import org.alfresco.service.cmr.thumbnail.FailedThumbnailInfo; import org.alfresco.service.cmr.thumbnail.ThumbnailException; import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails; import org.alfresco.service.cmr.thumbnail.ThumbnailService; @@ -62,8 +69,11 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest { private RenditionService renditionService; private ThumbnailService thumbnailService; + private ScriptThumbnailService scriptThumbnailService; private ScriptService scriptService; private MimetypeMap mimetypeMap; + private RetryingTransactionHelper transactionHelper; + private ServiceRegistry services; private NodeRef folder; /** @@ -78,8 +88,11 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest // Get the required services this.renditionService = (RenditionService) this.applicationContext.getBean("RenditionService"); this.thumbnailService = (ThumbnailService) this.applicationContext.getBean("ThumbnailService"); + this.scriptThumbnailService = (ScriptThumbnailService) this.applicationContext.getBean("thumbnailServiceScript"); this.mimetypeMap = (MimetypeMap) this.applicationContext.getBean("mimetypeService"); this.scriptService = (ScriptService) this.applicationContext.getBean("ScriptService"); + this.services = (ServiceRegistry) this.applicationContext.getBean("ServiceRegistry"); + this.transactionHelper = (RetryingTransactionHelper) this.applicationContext.getBean("retryingTransactionHelper"); // Create a folder and some content Map folderProps = new HashMap(1); @@ -247,6 +260,100 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest } } + /** + * @since 3.5.0 + */ + public void testCreateFailingThumbnail() throws Exception + { + final NodeRef corruptNode = this.createCorruptedContent(folder); + logger.debug("Running failing thumbnail on " + corruptNode); + + // Make sure the source node is correctly set up before we start + // It should not be renditioned and should not be marked as having any failed thumbnails. + assertFalse(nodeService.hasAspect(corruptNode, RenditionModel.ASPECT_RENDITIONED)); + assertFalse(nodeService.hasAspect(corruptNode, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE)); + + setComplete(); + endTransaction(); + + // Attempt to perform a thumbnail that we know will fail. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + ThumbnailDefinition thumbnailDef = thumbnailService.getThumbnailRegistry().getThumbnailDefinition("doclib"); + + Action createThumbnailAction = ThumbnailHelper.createCreateThumbnailAction(thumbnailDef, services); + actionService.executeAction(createThumbnailAction, corruptNode, true, true); + return null; + } + }); + // The thumbnail attempt has now failed. But a compensating action should have been scheduled that will mark the + // source node with a failure aspect. As that is an asynchronous action, we need to wait for that to complete. + + Thread.sleep(3000); // This should be long enough for the compensating action to run. + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + assertFalse("corrupt node should not have renditioned aspect", nodeService.hasAspect(corruptNode, RenditionModel.ASPECT_RENDITIONED)); + assertTrue("corrupt node should have failed thumbnails aspect", nodeService.hasAspect(corruptNode, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE)); + + Map failedThumbnails = thumbnailService.getFailedThumbnails(corruptNode); + assertEquals("Wrong number of failed thumbnails", 1, failedThumbnails.size()); + + assertTrue("Missing QName for failed thumbnail", failedThumbnails.containsKey("doclib")); + final FailedThumbnailInfo doclibFailureInfo = failedThumbnails.get("doclib"); + assertNotNull("Failure info was null", doclibFailureInfo); + assertEquals("Failure count was wrong.", 1, doclibFailureInfo.getFailureCount()); + assertEquals("thumbnail name was wrong.", "doclib", doclibFailureInfo.getThumbnailDefinitionName()); + + return null; + } + }); + + // If you uncomment this line and set the timeout to a value greater than ${system.thumbnail.minimum.retry.period} * 1000. + // Then the retry period will have passed, the below re-thumbnail attempt will be made and the test will fail with a + // failureCount == 2. + // + // Thread.sleep(150 * 1000); + + // Run the thumbnail again. It should not run because the action condition should prevent it. + // We can check that it does not run by ensuring the failureCount does not change. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + ThumbnailDefinition thumbnailDef = thumbnailService.getThumbnailRegistry().getThumbnailDefinition("doclib"); + + Action createThumbnailAction = ThumbnailHelper.createCreateThumbnailAction(thumbnailDef, services); + actionService.executeAction(createThumbnailAction, corruptNode, true, true); + return null; + } + }); + // Pause to let the async action be considered for running (but not run). + Thread.sleep(3000); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + Map failedThumbnails = thumbnailService.getFailedThumbnails(corruptNode); + assertEquals("Wrong number of failed thumbnails", 1, failedThumbnails.size()); + + assertTrue("Missing QName for failed thumbnail", failedThumbnails.containsKey("doclib")); + final FailedThumbnailInfo doclibFailureInfo = failedThumbnails.get("doclib"); + assertNotNull("Failure info was null", doclibFailureInfo); + assertEquals("Failure count was wrong.", 1, doclibFailureInfo.getFailureCount()); + assertEquals("thumbnail name was wrong.", "doclib", doclibFailureInfo.getThumbnailDefinitionName()); + + return null; + } + }); + } + + public void testThumbnailUpdate() throws Exception { checkTransformer(); @@ -341,7 +448,7 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest private void outputThumbnailTempContentLocation(NodeRef thumbnail, String ext, String message) throws IOException { - File tempFile = TempFileProvider.createTempFile("thumbnailServiceImplTest", "." + ext); + File tempFile = TempFileProvider.createTempFile("thumbnailServiceImplTest", "." + ext); ContentReader reader = this.contentService.getReader(thumbnail, ContentModel.PROP_CONTENT); reader.getContent(tempFile); System.out.println(message + ": " + tempFile.getPath()); @@ -374,7 +481,29 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest return node; } + + private NodeRef createCorruptedContent(NodeRef parentFolder) throws IOException + { + // The below pdf file has been truncated such that it is identifiable as a PDF but otherwise corrupt. + File corruptPdfFile = AbstractContentTransformerTest.loadNamedQuickTestFile("quickCorrupt.pdf"); + assertNotNull("Failed to load required test file.", corruptPdfFile); + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, "corrupt.pdf"); + NodeRef node = this.nodeService.createNode(parentFolder, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "quickCorrupt.pdf"), + ContentModel.TYPE_CONTENT, props).getChildRef(); + + nodeService.setProperty(node, ContentModel.PROP_CONTENT, new ContentData(null, + MimetypeMap.MIMETYPE_PDF, 0L, null)); + ContentWriter writer = contentService.getWriter(node, ContentModel.PROP_CONTENT, true); + writer.setMimetype(MimetypeMap.MIMETYPE_PDF); + writer.setEncoding("UTF-8"); + writer.putContent(corruptPdfFile); + + return node; + } + @SuppressWarnings("deprecation") public void testAutoUpdate() throws Exception { @@ -551,4 +680,36 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/thumbnail/script/test_thumbnailAPI.js"); this.scriptService.executeScript(location, model); } + + /** + * This test method tests the thumbnail placeholders which are handled in the {@link ScriptThumbnailService}. + * See ALF-6566. + */ + public void testPlaceHoldersByMimeType() throws Exception + { + // Retrieve the classpath paths for all the standard icon resources for doclib. + + final String standardDoclibIcon = "alfresco/thumbnail/thumbnail_placeholder_doclib.png"; + + // This used to be the cogs, but as of ALF-6566 is a generic document icon. + String doclibIcon = scriptThumbnailService.getPlaceHolderResourcePath("doclib"); + assertEquals(standardDoclibIcon, doclibIcon); + + // The same but with explicit null mimetype. + doclibIcon = scriptThumbnailService.getMimeAwarePlaceHolderResourcePath("doclib", null); + assertEquals(standardDoclibIcon, doclibIcon); + + // The icon for a .doc mime type - a sample, recognised mime type. + String docxDoclibIcon = scriptThumbnailService.getMimeAwarePlaceHolderResourcePath("doclib", MimetypeMap.MIMETYPE_WORD); + assertEquals("alfresco/thumbnail/thumbnail_placeholder_doclib_doc.png", docxDoclibIcon); + + // The icon for an unrecognised mime type. + String fallbackDoclibIcon = scriptThumbnailService.getMimeAwarePlaceHolderResourcePath("doclib", "application/wibble"); + assertEquals(standardDoclibIcon, fallbackDoclibIcon); + + // And one from the 'medium' set. + String mediumIcon = scriptThumbnailService.getPlaceHolderResourcePath("medium"); + final String standardMediumIcon = "alfresco/thumbnail/thumbnail_placeholder_medium.jpg"; // This one jpg, not png + assertEquals(standardMediumIcon, mediumIcon); + } } diff --git a/source/java/org/alfresco/repo/thumbnail/conditions/NodeEligibleForRethumbnailingEvaluator.java b/source/java/org/alfresco/repo/thumbnail/conditions/NodeEligibleForRethumbnailingEvaluator.java new file mode 100644 index 0000000000..533d7d54df --- /dev/null +++ b/source/java/org/alfresco/repo/thumbnail/conditions/NodeEligibleForRethumbnailingEvaluator.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.repo.thumbnail.conditions; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.evaluator.ActionConditionEvaluatorAbstractBase; +import org.alfresco.repo.content.transform.ContentTransformer; +import org.alfresco.repo.thumbnail.FailureHandlingOptions; +import org.alfresco.repo.thumbnail.ThumbnailDefinition; +import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.thumbnail.FailedThumbnailInfo; +import org.alfresco.service.cmr.thumbnail.ThumbnailService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This action evaluator is specifically created for the {@link ThumbnailService Thumbnail Service}. + * It is used to evaluate whether a {@link ThumbnailDefinition} should be executed - based on + * previous failed thumbnail attempts for that definition on that source node, as well as some + * configuration data. + *

    + * The behaviour is as follows: + *

      + *
    • All content nodes are eligible for thumbnailing initially. Of course thumbnails can only ever be + * attempted for those content mime types that have at least one registered and active {@link ContentTransformer}.
    • + *
    • If the first attempt to produce a thumbnail for a node fails, then it may be retried up to a maximum of + * {@link FailureHandlingOptions#getRetryCount() system.thumbnail.retryCount} times.
    • + *
    • These initial retries to produce a thumbnail will occur not more often than every + * {@link FailureHandlingOptions#getRetryPeriod() system.thumbnail.retryPeriod} seconds + * and will use which ever content transformers the {@link ContentService#getTransformer(String, String) content service} gives.
    • + *
    • If a thumbnail is not successfully produced for a node after these attempts then it is considered to be + * a 'difficult' piece of content with respect to thumbnailing and the assumption is that a thumbnail may + * never be available for it. However, in order to allow for the possibility of software upgrades or similiar, which may + * make the content thumbnailable at a later date, further attempts will be made, but at a much reduced frequency.
    • + *
    • Difficult pieces of content will not be attempted more often than every + * {@link FailureHandlingOptions#getQuietPeriod() system.thumbnail.quietPeriod} seconds.
    • + *
    • The attempts to thumbnail difficult pieces of content can be disabled by setting + * {@link FailureHandlingOptions#getQuietPeriodRetriesEnabled() system.thumbnail.quietPeriodRetriesEnabled} to false.
    • + *
    + * At all times, thumbnails will be attempted when a user navigates to a page which needs to show the relevant thumbnail (lazy production). + * + * @author Neil Mc Erlean. + * @since 3.5.0 + */ +public class NodeEligibleForRethumbnailingEvaluator extends ActionConditionEvaluatorAbstractBase +{ + private static Log logger = LogFactory.getLog(NodeEligibleForRethumbnailingEvaluator.class); + + /** + * Evaluator constants + */ + public final static String NAME = "node-eligible-for-rethumbnailing-evaluator"; + + public final static String PARAM_THUMBNAIL_DEFINITION_NAME = "thumbnail-definition-name"; + + public final static String PARAM_RETRY_PERIOD = "retry-period"; + public final static String PARAM_RETRY_COUNT = "retry-count"; + public final static String PARAM_QUIET_PERIOD = "quiet-period"; + public final static String PARAM_QUIET_PERIOD_RETRIES_ENABLED = "quiet-period-retries-enabled"; + + protected NodeService nodeService; + protected ThumbnailService thumbnailService; + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setThumbnailService(ThumbnailService thumbnailService) + { + this.thumbnailService = thumbnailService; + } + + /** + * Add parameter definitions + */ + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_THUMBNAIL_DEFINITION_NAME, DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_THUMBNAIL_DEFINITION_NAME))); + + paramList.add(new ParameterDefinitionImpl(PARAM_RETRY_PERIOD, DataTypeDefinition.LONG, true, getParamDisplayLabel(PARAM_RETRY_PERIOD))); + paramList.add(new ParameterDefinitionImpl(PARAM_RETRY_COUNT, DataTypeDefinition.INT, true, getParamDisplayLabel(PARAM_RETRY_COUNT))); + + paramList.add(new ParameterDefinitionImpl(PARAM_QUIET_PERIOD, DataTypeDefinition.LONG, true, getParamDisplayLabel(PARAM_QUIET_PERIOD))); + paramList.add(new ParameterDefinitionImpl(PARAM_QUIET_PERIOD_RETRIES_ENABLED, DataTypeDefinition.BOOLEAN, true, getParamDisplayLabel(PARAM_QUIET_PERIOD_RETRIES_ENABLED))); + } + + /** + * @see ActionConditionEvaluatorAbstractBase#evaluateImpl(ActionCondition, NodeRef) + */ + public boolean evaluateImpl(ActionCondition actionCondition, NodeRef actionedUponNodeRef) + { + if (!this.nodeService.exists(actionedUponNodeRef)) + { + return false; + } + + Serializable paramThumbnailDefnName = actionCondition.getParameterValue(PARAM_THUMBNAIL_DEFINITION_NAME); + + Serializable paramRetryPeriod = actionCondition.getParameterValue(PARAM_RETRY_PERIOD); + Serializable paramRetryCount = actionCondition.getParameterValue(PARAM_RETRY_COUNT); + Serializable paramQuietPeriod = actionCondition.getParameterValue(PARAM_QUIET_PERIOD); + + final Serializable parameterValue = actionCondition.getParameterValue(PARAM_QUIET_PERIOD_RETRIES_ENABLED); + Serializable paramQuietPeriodRetriesEnabled = parameterValue != null ? parameterValue : FailureHandlingOptions.DEFAULT_QUIET_PERIOD_RETRIES_ENABLED; + + + + final QName thumbnailDefnQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, (String)paramThumbnailDefnName); + + // If there are no previous failed thumbnail attempts for this thumbnail definition, + // then the node is always eligible for a first try. + Map failures = thumbnailService.getFailedThumbnails(actionedUponNodeRef); + if (failures.isEmpty() || !failures.containsKey(thumbnailDefnQName.getLocalName())) + { + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Node ").append(actionedUponNodeRef).append(" has no matching ").append(ContentModel.ASSOC_FAILED_THUMBNAIL) + .append(" child."); + logger.debug(msg.toString()); + } + return true; + } + + + final FailedThumbnailInfo failedThumbnailInfo = failures.get(thumbnailDefnQName.getLocalName()); + + // There is a cm:failedThumbnail child, so there has been at least one failed execution of the given + // thumbnail definition at some point. + if (failedThumbnailInfo.getMostRecentFailure() == null) + { + // The property should never be null like this, but just in case. + return true; + } + + // So how long ago did the given thumbnail definition fail? + long nowMs = new Date().getTime(); + long failureTimeMs = failedThumbnailInfo.getMostRecentFailure().getTime(); + final long timeSinceLastFailureMs = nowMs - failureTimeMs; + + // And how many failures have there been? + final int failureCount = failedThumbnailInfo.getFailureCount(); + + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Comparing failure time of ") + .append(failedThumbnailInfo.getMostRecentFailure()).append(" to now. Difference is ") + .append(timeSinceLastFailureMs).append(" ms. ").append(failureCount).append(" existing failures."); + logger.debug(msg.toString()); + } + + if (failureCount >= (Integer)paramRetryCount) + { + boolean quietPeriodRetriesEnabled = (Boolean)paramQuietPeriodRetriesEnabled; + return quietPeriodRetriesEnabled && timeSinceFailureExceedsLimit(paramQuietPeriod, timeSinceLastFailureMs); + } + else + { + return timeSinceFailureExceedsLimit(paramRetryPeriod, timeSinceLastFailureMs); + } + } + + private boolean timeSinceFailureExceedsLimit(Serializable failurePeriod, long timeSinceFailure) + { + // Is that time period greater than the specified offset? + // We'll allow for -ve offset values. + Long offsetLong = (Long)failurePeriod; + long absOffset = Math.abs(offsetLong); + + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Offset is ") + .append(offsetLong).append(" ms."); + logger.debug(msg.toString()); + } + + return timeSinceFailure > absOffset; + } +} diff --git a/source/java/org/alfresco/repo/thumbnail/conditions/NodeEligibleForRethumbnailingEvaluatorTest.java b/source/java/org/alfresco/repo/thumbnail/conditions/NodeEligibleForRethumbnailingEvaluatorTest.java new file mode 100644 index 0000000000..a4758a077f --- /dev/null +++ b/source/java/org/alfresco/repo/thumbnail/conditions/NodeEligibleForRethumbnailingEvaluatorTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.repo.thumbnail.conditions; + +import java.util.Date; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionConditionImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.thumbnail.FailureHandlingOptions; +import org.alfresco.service.cmr.action.ActionCondition; +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.thumbnail.FailedThumbnailInfo; +import org.alfresco.service.cmr.thumbnail.ThumbnailService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.GUID; + +/** + * This class tests {@link NodeEligibleForRethumbnailingEvaluator}. + * + * @author Neil Mc Erlean + * @since 3.5.0 + */ +public class NodeEligibleForRethumbnailingEvaluatorTest extends BaseSpringTest +{ + private final QName thumbnailDef1 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "thumbDef1"); + + private NodeService nodeService; + private ThumbnailService thumbnailService; + private StoreRef testStoreRef; + private NodeRef rootNodeRef; + + private FailureHandlingOptions failureHandlingOptions; + + /** + * A node with no thumbnails on it & no previous attempts to produce thumbnails. + */ + private NodeRef newUnthumbnailedNodeRef; + + /** + * No thumbnails. 1 failed attempt 2 seconds ago. + */ + private NodeRef recentlyFailedNodeRef; + + /** + * @see org.springframework.test.AbstractTransactionalSpringContextTests#onSetUpInTransaction() + */ + @SuppressWarnings("deprecation") + @Override + protected void onSetUpInTransaction() throws Exception + { + final Date now = new Date(); + final Date twoSecondsAgo = new Date(now.getTime() - 2000); + + this.nodeService = (NodeService)this.applicationContext.getBean("nodeService"); + this.thumbnailService = (ThumbnailService)this.applicationContext.getBean("thumbnailService"); + this.failureHandlingOptions = (FailureHandlingOptions) this.applicationContext.getBean("standardFailureOptions"); + + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + // Create the store and get the root node + this.testStoreRef = this.nodeService.createStore( + StoreRef.PROTOCOL_WORKSPACE, "Test_" + + System.currentTimeMillis()); + this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); + + // We'll fake this. We won't actually run any thumbnails - just set up the various aspect and child-node + // conditions we need to test the evaluator. + this.newUnthumbnailedNodeRef = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}newUnthumbnailedNodeRef"), + ContentModel.TYPE_CONTENT).getChildRef(); + + this.recentlyFailedNodeRef = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}recentlyFailedNodeRef"), + ContentModel.TYPE_CONTENT).getChildRef(); + nodeService.addAspect(recentlyFailedNodeRef, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE, null); + NodeRef thumbDef1FailureNode = nodeService.createNode(recentlyFailedNodeRef, + ContentModel.ASSOC_FAILED_THUMBNAIL, + thumbnailDef1, + ContentModel.TYPE_FAILED_THUMBNAIL).getChildRef(); + nodeService.setProperty(thumbDef1FailureNode, ContentModel.PROP_FAILED_THUMBNAIL_TIME, twoSecondsAgo); + nodeService.setProperty(thumbDef1FailureNode, ContentModel.PROP_FAILURE_COUNT, 1); + } + + @Override + public void onTearDownInTransaction() + { + nodeService.deleteStore(testStoreRef); + } + + @SuppressWarnings("deprecation") + public void testNodeWithNoFailedThumbnails() + { + // Such a node is always eligible for thumbnailing. + ActionCondition condition = new ActionConditionImpl(GUID.generate(), NodeEligibleForRethumbnailingEvaluator.NAME); + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_THUMBNAIL_DEFINITION_NAME, thumbnailDef1.getLocalName()); + // Offset values don't matter here. + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_RETRY_PERIOD, 0L); + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_RETRY_COUNT, + failureHandlingOptions.getRetryCount()); + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_QUIET_PERIOD, 0L); + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_QUIET_PERIOD_RETRIES_ENABLED, true); + + NodeEligibleForRethumbnailingEvaluator evaluator = + (NodeEligibleForRethumbnailingEvaluator)this.applicationContext.getBean(NodeEligibleForRethumbnailingEvaluator.NAME); + + assertTrue(evaluator.evaluate(condition, newUnthumbnailedNodeRef)); + } + + public void testNodeWithFailedThumbnails() + { + // A "non-difficult" node is one which is not yet known to be difficult to thumbnail. + // In other words it is one which has previously failed to thumbnail, but which has not yet + // hit the retryCount limit for initial retries. + + NodeEligibleForRethumbnailingEvaluator evaluator = + (NodeEligibleForRethumbnailingEvaluator)this.applicationContext.getBean(NodeEligibleForRethumbnailingEvaluator.NAME); + + // Evaluate the thumbnail definition which has failed. + // + // 1. A node that has failed once - and more recently than the limit. + ActionCondition condition = new ActionConditionImpl(GUID.generate(), NodeEligibleForRethumbnailingEvaluator.NAME); + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_THUMBNAIL_DEFINITION_NAME, thumbnailDef1.getLocalName()); + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_RETRY_PERIOD, + failureHandlingOptions.getRetryPeriod() * 1000); + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_RETRY_COUNT, + failureHandlingOptions.getRetryCount()); + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_QUIET_PERIOD, + failureHandlingOptions.getQuietPeriod() * 1000); + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_QUIET_PERIOD_RETRIES_ENABLED, true); + + assertFalse(evaluator.evaluate(condition, recentlyFailedNodeRef)); + + // 2. A node that has failed once - but longer ago than the lower limit. + Map failures = thumbnailService.getFailedThumbnails(recentlyFailedNodeRef); + assertFalse(failures.isEmpty()); + final FailedThumbnailInfo failedThumbnailInfo = failures.get(thumbnailDef1.getLocalName()); + + final long timeBeforeTheLimit = new Date().getTime() - (failureHandlingOptions.getRetryPeriod() * 1000l) - 5000l; + nodeService.setProperty(failedThumbnailInfo.getFailedThumbnailNode(), ContentModel.PROP_FAILED_THUMBNAIL_TIME, timeBeforeTheLimit); + + assertTrue(evaluator.evaluate(condition, recentlyFailedNodeRef)); + + + + // 3. If the same node had failed retryCount times, it would not be eligible. + // At this point it would be a "difficult" document. + nodeService.setProperty(failedThumbnailInfo.getFailedThumbnailNode(), ContentModel.PROP_FAILURE_COUNT, failureHandlingOptions.getRetryCount()); + + assertFalse(evaluator.evaluate(condition, recentlyFailedNodeRef)); + + + // 4. If it had failed retryCount times, but its last failure time was more than + // quietPeriod seconds ago, then it would be eligible. + final long timeBeforeTheLongLimit = new Date().getTime() - (failureHandlingOptions.getQuietPeriod() * 1000l) - 5000l; + nodeService.setProperty(failedThumbnailInfo.getFailedThumbnailNode(), ContentModel.PROP_FAILED_THUMBNAIL_TIME, timeBeforeTheLongLimit); + + assertTrue(evaluator.evaluate(condition, recentlyFailedNodeRef)); + + // 5. Unless the retries during the quiet period are disabled... + condition.setParameterValue(NodeEligibleForRethumbnailingEvaluator.PARAM_QUIET_PERIOD_RETRIES_ENABLED, false); + + assertFalse(evaluator.evaluate(condition, recentlyFailedNodeRef)); + } +} diff --git a/source/java/org/alfresco/repo/thumbnail/script/ScriptThumbnailService.java b/source/java/org/alfresco/repo/thumbnail/script/ScriptThumbnailService.java index 416f752a28..39ace4ca58 100644 --- a/source/java/org/alfresco/repo/thumbnail/script/ScriptThumbnailService.java +++ b/source/java/org/alfresco/repo/thumbnail/script/ScriptThumbnailService.java @@ -18,6 +18,9 @@ */ package org.alfresco.repo.thumbnail.script; +import java.text.MessageFormat; +import java.util.Map; + import org.alfresco.repo.jscript.BaseScopableProcessorExtension; import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.service.ServiceRegistry; @@ -60,7 +63,7 @@ public class ScriptThumbnailService extends BaseScopableProcessorExtension * Returns null if none set. * * @param thumbnailName the thumbnail name - * @return String the place holder thumbnail resource path, null if none set + * @return String the place holder thumbnail resource path, null if none set */ public String getPlaceHolderResourcePath(String thumbnailName) { @@ -73,4 +76,72 @@ public class ScriptThumbnailService extends BaseScopableProcessorExtension return result; } + /** + * Gets the resource path for the place holder thumbnail for the given named thumbnail and the given mime type. + * If there is no icon available for the specified MIME type, a generic icon will be used instead. + * The generic icon is that returned by {@link getPlaceHolderResourcePath(String)} + * If neither a MIME-specific icon nor a generic icon is available, null is returned. + * + * @param thumbnailName the thumbnail name + * @param mimetype the mimetype of the piece of content. + * @return String the place holder thumbnail resource path + * @see #getPlaceHolderResourcePath(String) + */ + public String getMimeAwarePlaceHolderResourcePath(String thumbnailName, String mimetype) + { + String result = null; + + // Sanity check + if (mimetype == null || mimetype.trim().length() == 0) + { + return getPlaceHolderResourcePath(thumbnailName); + } + + Map extensionsByMimetype = serviceRegistry.getMimetypeService().getExtensionsByMimetype(); + // We get the extension for the mime type directly from the Map as we need + // to know if a mimetype is not recognised by the system. (The MimetypeService gives us ".bin" + // for unrecognised mimetypes.) + + // The mime fragment is the part of the resource path that refers to the mime type + // e.g. "_pdf" in alfresco/thumbnail/thumbnail_placeholder_doclib_pdf.png + + String extension = extensionsByMimetype.get(mimetype); + + if (extension != null) + { + StringBuilder sb = new StringBuilder(); + sb.append("_").append(String.valueOf(extension)); + String mimeFragment = sb.toString(); + + ThumbnailDefinition details = serviceRegistry.getThumbnailService().getThumbnailRegistry().getThumbnailDefinition(thumbnailName); + if (details != null) + { + String resourcePath = details.getMimeAwarePlaceHolderResourcePath(); + if (resourcePath != null) + { + // It's possible that the mimetype service recognises mime types for which we have no icon. + String formattedResourcePath = MessageFormat.format(resourcePath, mimeFragment); + boolean iconResourceExists = classpathResourceExists("/" + formattedResourcePath); + + if (iconResourceExists) + { + result = formattedResourcePath; + } + } + } + } + + return result == null ? getPlaceHolderResourcePath(thumbnailName) : result; + } + + /** + * This method returns true if the specified classpath resource exists, + * else false. + */ + private boolean classpathResourceExists(String resourcePath) + { + boolean result = this.getClass().getResource(resourcePath) != null; + + return result; + } } diff --git a/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java b/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java index c5aa69c539..0a1a4be0b7 100644 --- a/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java +++ b/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java @@ -65,6 +65,8 @@ public abstract class AlfrescoTransactionSupport /** resource key to store the transaction synchronizer instance */ private static final String RESOURCE_KEY_TXN_SYNCH = "txnSynch"; + /** resource binding during after-completion phase */ + private static final String RESOURCE_KEY_TXN_COMPLETING = "AlfrescoTransactionSupport.txnCompleting"; private static Log logger = LogFactory.getLog(AlfrescoTransactionSupport.class); @@ -160,7 +162,12 @@ public abstract class AlfrescoTransactionSupport return TxnReadState.TXN_NONE; } // Find the read-write state of the txn - if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) + if (AlfrescoTransactionSupport.getResource(RESOURCE_KEY_TXN_COMPLETING) != null) + { + // Transaction is completing. For all intents and purposes, we are not in a transaction. + return TxnReadState.TXN_NONE; + } + else if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { return TxnReadState.TXN_READ_ONLY; } @@ -180,16 +187,19 @@ public abstract class AlfrescoTransactionSupport */ public static void checkTransactionReadState(boolean requireReadWrite) { - if (!TransactionSynchronizationManager.isSynchronizationActive()) + TxnReadState readState = AlfrescoTransactionSupport.getTransactionReadState(); + switch (readState) { - throw new IllegalStateException( - "The current operation requires an active " + - (requireReadWrite ? "read-write" : "") + - "transaction."); - } - if (TransactionSynchronizationManager.isCurrentTransactionReadOnly() && requireReadWrite) - { - throw new IllegalStateException("The current operation requires an active read-write transaction."); + case TXN_NONE: + throw new IllegalStateException( + "The current operation requires an active " + + (requireReadWrite ? "read-write" : "") + + "transaction."); + case TXN_READ_ONLY: + if (requireReadWrite) + { + throw new IllegalStateException("The current operation requires an active read-write transaction."); + } } } @@ -776,6 +786,10 @@ public abstract class AlfrescoTransactionSupport logger.debug("After completion (" + statusStr + "): " + this); } + // Force any queries for read-write state to return TXN_READ_ONLY + // This will be cleared with the synchronization, so we don't need to clear it out + AlfrescoTransactionSupport.bindResource(RESOURCE_KEY_TXN_COMPLETING, Boolean.TRUE); + // Clean up the transactional caches for (TransactionalCache cache : transactionalCaches) { @@ -796,6 +810,26 @@ public abstract class AlfrescoTransactionSupport } } + // commit/rollback Lucene + for (LuceneIndexerAndSearcher lucene : lucenes) + { + try + { + if (status == TransactionSynchronization.STATUS_COMMITTED) + { + lucene.commit(); + } + else + { + lucene.rollback(); + } + } + catch (RuntimeException e) + { + logger.error("After completion (" + statusStr + ") Lucene exception", e); + } + } + List iterableListeners = getListenersIterable(); // notify listeners if (status == TransactionSynchronization.STATUS_COMMITTED) @@ -831,26 +865,6 @@ public abstract class AlfrescoTransactionSupport } } - // commit/rollback Lucene - for (LuceneIndexerAndSearcher lucene : lucenes) - { - try - { - if (status == TransactionSynchronization.STATUS_COMMITTED) - { - lucene.commit(); - } - else - { - lucene.rollback(); - } - } - catch (RuntimeException e) - { - logger.error("After completion (" + statusStr + ") Lucene exception", e); - } - } - // clear the thread's registrations and synchronizations AlfrescoTransactionSupport.clearSynchronization(); } diff --git a/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupportTest.java b/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupportTest.java index 702f8dfb5c..8e794ca6d1 100644 --- a/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupportTest.java +++ b/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupportTest.java @@ -235,10 +235,23 @@ public class AlfrescoTransactionSupportTest extends TestCase public void testReadWriteStateRetrieval() throws Exception { + final TxnReadState[] postCommitReadState = new TxnReadState[1]; + final TransactionListenerAdapter getReadStatePostCommit = new TransactionListenerAdapter() + { + @Override + public void afterCommit() + { + postCommitReadState[0] = AlfrescoTransactionSupport.getTransactionReadState(); + } + }; + RetryingTransactionCallback getReadStateWork = new RetryingTransactionCallback() { public TxnReadState execute() throws Exception { + // Register to list to post-commit + AlfrescoTransactionSupport.bindListener(getReadStatePostCommit); + return AlfrescoTransactionSupport.getTransactionReadState(); } }; @@ -246,12 +259,15 @@ public class AlfrescoTransactionSupportTest extends TestCase // Check TXN_NONE TxnReadState checkTxnReadState = AlfrescoTransactionSupport.getTransactionReadState(); assertEquals("Expected 'no transaction'", TxnReadState.TXN_NONE, checkTxnReadState); + assertNull("Expected no post-commit read state", postCommitReadState[0]); // Check TXN_READ_ONLY checkTxnReadState = transactionService.getRetryingTransactionHelper().doInTransaction(getReadStateWork, true); assertEquals("Expected 'read-only transaction'", TxnReadState.TXN_READ_ONLY, checkTxnReadState); + assertEquals("Expected 'no transaction'", TxnReadState.TXN_NONE, postCommitReadState[0]); // check TXN_READ_WRITE checkTxnReadState = transactionService.getRetryingTransactionHelper().doInTransaction(getReadStateWork, false); assertEquals("Expected 'read-write transaction'", TxnReadState.TXN_READ_WRITE, checkTxnReadState); + assertEquals("Expected 'no transaction'", TxnReadState.TXN_NONE, postCommitReadState[0]); } public void testResourceHelper() throws Exception diff --git a/source/java/org/alfresco/repo/transaction/DummyTransactionService.java b/source/java/org/alfresco/repo/transaction/DummyTransactionService.java index 087adf1026..dee7ec9482 100644 --- a/source/java/org/alfresco/repo/transaction/DummyTransactionService.java +++ b/source/java/org/alfresco/repo/transaction/DummyTransactionService.java @@ -42,35 +42,54 @@ public class DummyTransactionService implements TransactionService public void setTransactionTimeout(int arg0) {}; }; + @Override + public boolean getAllowWrite() + { + return true; + } + + @Override public boolean isReadOnly() { return false; } - public void setReadOnly(boolean readOnly) - { - } - + @Override public UserTransaction getUserTransaction() { return txn; } + @Override public UserTransaction getUserTransaction(boolean readOnly) { return txn; } + @Override + public UserTransaction getUserTransaction(boolean readOnly, boolean ignoreSystemReadOnly) + { + return txn; + } + + @Override public UserTransaction getNonPropagatingUserTransaction() { return txn; } + @Override public UserTransaction getNonPropagatingUserTransaction(boolean readOnly) { return txn; } + @Override + public UserTransaction getNonPropagatingUserTransaction(boolean readOnly, boolean ignoreSystemReadOnly) + { + return txn; + } + public RetryingTransactionHelper getRetryingTransactionHelper() { RetryingTransactionHelper helper = new RetryingTransactionHelper(); diff --git a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java index 09e9dcd3d5..d1e0ef646a 100644 --- a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java +++ b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java @@ -115,8 +115,6 @@ public class RetryingTransactionHelper */ private TransactionService txnService; -// /** Performs post-failure exception neatening */ -// private ExceptionTransformer exceptionTransformer; /** The maximum number of retries. -1 for infinity. */ private int maxRetries; /** The minimum time to wait between retries. */ @@ -143,6 +141,11 @@ public class RetryingTransactionHelper */ private boolean readOnly; + /** + * Whether the system's read-only state should be ignored + */ + private boolean forceWritable; + /** * Random number generator for retry delays. */ @@ -173,19 +176,9 @@ public class RetryingTransactionHelper this.minRetryWaitMs = 100; this.maxRetryWaitMs = 2000; this.retryWaitIncrementMs = 100; + this.forceWritable = false; } - // Setters. - -// /** -// * Optionally set the component that will transform or neaten any exceptions that are -// * propagated. -// */ -// public void setExceptionTransformer(ExceptionTransformer exceptionTransformer) -// { -// this.exceptionTransformer = exceptionTransformer; -// } -// /** * Set the TransactionService. */ @@ -233,6 +226,20 @@ public class RetryingTransactionHelper { this.readOnly = readOnly; } + + /** + * Override to allow the transactions to be writable regardless of the system read-only mode. + *

    + * NOTE: This method may not be used to circumvent the Alfresco License policy. + * + * @param forceWritable true to force transactions to be writable + * regardless of system read-only mode + */ + public void setForceWritable(boolean forceWritable) + { + this.forceWritable = forceWritable; + this.readOnly = false; + } /** * Execute a callback in a transaction until it succeeds, fails @@ -365,8 +372,9 @@ public class RetryingTransactionHelper { if (startingNew) { - txn = requiresNew ? txnService.getNonPropagatingUserTransaction(readOnly) : txnService - .getUserTransaction(readOnly); + txn = requiresNew ? + txnService.getNonPropagatingUserTransaction(readOnly, forceWritable) : + txnService.getUserTransaction(readOnly, forceWritable); txn.begin(); // Wrap it to protect it UserTransactionProtectionAdvise advise = new UserTransactionProtectionAdvise(); diff --git a/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java b/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java index f5f2534437..75dce84f8d 100644 --- a/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java +++ b/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java @@ -18,13 +18,23 @@ */ package org.alfresco.repo.transaction; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + import javax.transaction.UserTransaction; import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.VmShutdownListener; import org.alfresco.util.transaction.SpringAwareUserTransaction; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; @@ -45,16 +55,31 @@ public class TransactionServiceImpl implements TransactionService private int minRetryWaitMs = -1; private int maxRetryWaitMs = -1; private int retryWaitIncrementMs = -1; + + private static final Log logger = LogFactory.getLog(TransactionServiceImpl.class); // SysAdmin cache - used to cluster certain configuration parameters private SysAdminParams sysAdminParams; - private boolean allowWrite; + + // Veto for allow write + private Set writeVeto = new HashSet(); + private final QName generalVetoName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "GeneralVeto"); + + private ReadLock vetoReadLock; + private WriteLock vetoWriteLock; + + /** + * Construct defaults + */ + public TransactionServiceImpl() + { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + vetoReadLock = lock.readLock(); + vetoWriteLock = lock.writeLock(); + } /** * Set the transaction manager to use - * - * @param transactionManager - * platform transaction manager */ public void setTransactionManager(PlatformTransactionManager transactionManager) { @@ -77,26 +102,101 @@ public class TransactionServiceImpl implements TransactionService this.sysAdminParams = sysAdminParams; } + /** + * {@inheritDoc} + */ + @Override + public boolean getAllowWrite() + { + vetoReadLock.lock(); + try + { + return writeVeto.isEmpty(); + } + finally + { + vetoReadLock.unlock(); + } + } + /** * Set the read-only mode for all generated transactions. + *

    + * Intended for use by spring configuration only. Alfresco code should call the method which + * specifies a veto name. * - * @param allowWrite - * false if all transactions must be read-only + * @param allowWrite false if all transactions must be read-only */ public void setAllowWrite(boolean allowWrite) { - this.allowWrite = allowWrite; + setAllowWrite(allowWrite, generalVetoName); } + + /** + * Set the read-only mode for all generated transactions. + *

    + * By default read/write transactions are allowed however vetos may be applied that make the + * transactions read only. + *

    + * Prevent writes by calling allowWrite with false and a given veto name. + *

    + * The veto is removed by calling allowWrite with true for the given veto name + * when all vetos are removed then read/write transactions are allowed. + * + * @param allowWrite + * false if all transactions must be read-only + * @param nameOfVeto + * the name of the veto + */ + public void setAllowWrite(boolean allowWrite, QName nameOfVeto) + { + if(logger.isDebugEnabled()) + { + logger.debug("setAllowWrite:" + allowWrite + ", name of veto:" + nameOfVeto); + } + vetoWriteLock.lock(); + try + { + if(allowWrite) + { + writeVeto.remove(nameOfVeto); + } + else + { + writeVeto.add(nameOfVeto); + } + } + finally + { + vetoWriteLock.unlock(); + } + } + /** + * {@inheritDoc} + */ public boolean isReadOnly() { if (shutdownListener.isVmShuttingDown()) { return true; } - // Make the repo writable to the system user, so that e.g. the allow write flag can still be edited by JMX - return !this.allowWrite || !this.authenticationContext.isCurrentUserTheSystemUser() - && !this.sysAdminParams.getAllowWrite(); + vetoReadLock.lock(); + try + { + if (this.authenticationContext.isCurrentUserTheSystemUser()) + { + return false; + } + else + { + return !writeVeto.isEmpty() || !this.sysAdminParams.getAllowWrite(); + } + } + finally + { + vetoReadLock.unlock(); + } } /** @@ -136,10 +236,7 @@ public class TransactionServiceImpl implements TransactionService */ public UserTransaction getUserTransaction() { - SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, isReadOnly(), - TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRED, - TransactionDefinition.TIMEOUT_DEFAULT); - return txn; + return getUserTransaction(false, false); } /** @@ -147,10 +244,32 @@ public class TransactionServiceImpl implements TransactionService */ public UserTransaction getUserTransaction(boolean readOnly) { - SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, (readOnly | isReadOnly()), - TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRED, - TransactionDefinition.TIMEOUT_DEFAULT); - return txn; + return getUserTransaction(readOnly, false); + } + + /** + * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED + */ + public UserTransaction getUserTransaction(boolean readOnly, boolean ignoreSystemReadOnly) + { + if (ignoreSystemReadOnly) + { + SpringAwareUserTransaction txn = new SpringAwareUserTransaction( + transactionManager, + (readOnly), + TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRED, + TransactionDefinition.TIMEOUT_DEFAULT); + return txn; + } + else + { + SpringAwareUserTransaction txn = new SpringAwareUserTransaction( + transactionManager, + (readOnly | isReadOnly()), + TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRED, + TransactionDefinition.TIMEOUT_DEFAULT); + return txn; + } } /** @@ -158,10 +277,7 @@ public class TransactionServiceImpl implements TransactionService */ public UserTransaction getNonPropagatingUserTransaction() { - SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, isReadOnly(), - TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRES_NEW, - TransactionDefinition.TIMEOUT_DEFAULT); - return txn; + return getNonPropagatingUserTransaction(false, false); } /** @@ -169,10 +285,33 @@ public class TransactionServiceImpl implements TransactionService */ public UserTransaction getNonPropagatingUserTransaction(boolean readOnly) { - SpringAwareUserTransaction txn = new SpringAwareUserTransaction(transactionManager, (readOnly | isReadOnly()), - TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRES_NEW, - TransactionDefinition.TIMEOUT_DEFAULT); - return txn; + return getNonPropagatingUserTransaction(readOnly, false); + } + + /** + * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW + */ + @Override + public UserTransaction getNonPropagatingUserTransaction(boolean readOnly, boolean ignoreSystemReadOnly) + { + if (ignoreSystemReadOnly) + { + SpringAwareUserTransaction txn = new SpringAwareUserTransaction( + transactionManager, + (readOnly), + TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRES_NEW, + TransactionDefinition.TIMEOUT_DEFAULT); + return txn; + } + else + { + SpringAwareUserTransaction txn = new SpringAwareUserTransaction( + transactionManager, + (readOnly | isReadOnly()), + TransactionDefinition.ISOLATION_DEFAULT, TransactionDefinition.PROPAGATION_REQUIRES_NEW, + TransactionDefinition.TIMEOUT_DEFAULT); + return txn; + } } /** diff --git a/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java b/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java index cd508ea1d4..60eba830b2 100644 --- a/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java +++ b/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java @@ -30,6 +30,8 @@ import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.ReadOnlyServerException; import org.alfresco.util.ApplicationContextHelper; import org.hibernate.dialect.Dialect; @@ -53,6 +55,8 @@ public class TransactionServiceImplTest extends TestCase private TransactionServiceImpl transactionService; private NodeService nodeService; + private final QName vetoName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "TransactionServiceImplTest"); + private Dialect dialect; public void setUp() throws Exception @@ -60,7 +64,7 @@ public class TransactionServiceImplTest extends TestCase transactionManager = (PlatformTransactionManager) ctx.getBean("transactionManager"); transactionService = new TransactionServiceImpl(); transactionService.setTransactionManager(transactionManager); - transactionService.setAllowWrite(true); + transactionService.setAllowWrite(true, vetoName); transactionService.setAuthenticationContext((AuthenticationContext) ctx.getBean("authenticationContext")); transactionService.setSysAdminParams((SysAdminParams) ctx.getBean("sysAdminParams")); @@ -128,7 +132,7 @@ public class TransactionServiceImplTest extends TestCase public void testReadOnlyTxn() throws Exception { // start a read-only transaction - transactionService.setAllowWrite(false); + transactionService.setAllowWrite(false, vetoName); UserTransaction txn = transactionService.getUserTransaction(); txn.begin(); @@ -184,6 +188,7 @@ public class TransactionServiceImplTest extends TestCase } finally { + transactionService.setAllowWrite(true, vetoName); try { txn.rollback(); @@ -192,6 +197,46 @@ public class TransactionServiceImplTest extends TestCase } } + /** + * Test the write veto + * @throws Exception + */ + public void testReadOnlyVetoTxn() throws Exception + { + + QName v1 = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "V1"); + QName v2 = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "V2"); + QName v3 = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "V2"); + try + { + // start a read-only transaction + transactionService.setAllowWrite(false, v1); + transactionService.setAllowWrite(false, v2); + + assertFalse("v1 AND v2 veto not read only", transactionService.getAllowWrite()); + + transactionService.setAllowWrite(true, v2); + assertFalse("v1 not read only", transactionService.getAllowWrite()); + + transactionService.setAllowWrite(true, v1); + assertTrue("v1 still read only", transactionService.getAllowWrite()); + + /** + * Remove non existent veto + */ + transactionService.setAllowWrite(true, v3); + assertTrue("v3 veto", transactionService.getAllowWrite()); + + + } + finally + { + transactionService.setAllowWrite(true, v1); + transactionService.setAllowWrite(true, v2); + transactionService.setAllowWrite(true, v3); + } + } + public void testGetRetryingTransactionHelper() { RetryingTransactionCallback callback = new RetryingTransactionCallback() @@ -205,11 +250,11 @@ public class TransactionServiceImplTest extends TestCase assertFalse("Retriers must be new instances", transactionService.getRetryingTransactionHelper() == transactionService.getRetryingTransactionHelper()); - transactionService.setAllowWrite(true); + transactionService.setAllowWrite(true, vetoName); transactionService.getRetryingTransactionHelper().doInTransaction(callback, true); transactionService.getRetryingTransactionHelper().doInTransaction(callback, false); - transactionService.setAllowWrite(false); + transactionService.setAllowWrite(false, vetoName); transactionService.getRetryingTransactionHelper().doInTransaction(callback, true); try { @@ -220,5 +265,13 @@ public class TransactionServiceImplTest extends TestCase { // Expected } + + // Now check that we can force writable transactions + RetryingTransactionHelper helper = transactionService.getRetryingTransactionHelper(); + helper.setForceWritable(true); + helper.doInTransaction(callback, true); + helper.doInTransaction(callback, false); + + transactionService.setAllowWrite(true, vetoName); } } diff --git a/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java index 1e09a0e135..afb769efff 100644 --- a/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java +++ b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java @@ -40,6 +40,7 @@ import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.transfer.TransferException; import org.alfresco.service.cmr.transfer.TransferProgress; import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.service.cmr.transfer.TransferVersion; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.util.PropertyCheck; import org.alfresco.util.json.ExceptionJsonSerializer; @@ -234,7 +235,7 @@ public class HttpClientTransmitterImpl implements TransferTransmitter return hostConfig; } - public Transfer begin(TransferTarget target, String fromRepositoryId) throws TransferException + public Transfer begin(TransferTarget target, String fromRepositoryId, TransferVersion fromVersion) throws TransferException { PostMethod beginRequest = getPostMethod(); try @@ -247,25 +248,51 @@ public class HttpClientTransmitterImpl implements TransferTransmitter { beginRequest.setRequestBody( new NameValuePair[] { new NameValuePair(TransferCommons.PARAM_FROM_REPOSITORYID, fromRepositoryId), - new NameValuePair(TransferCommons.PARAM_ALLOW_TRANSFER_TO_SELF, "false")}); + new NameValuePair(TransferCommons.PARAM_ALLOW_TRANSFER_TO_SELF, "false"), + new NameValuePair(TransferCommons.PARAM_VERSION_MAJOR, fromVersion.getVersionMajor()), + new NameValuePair(TransferCommons.PARAM_VERSION_MINOR, fromVersion.getVersionMinor()), + new NameValuePair(TransferCommons.PARAM_VERSION_REVISION, fromVersion.getVersionRevision()), + new NameValuePair(TransferCommons.PARAM_VERSION_EDITION, fromVersion.getEdition()) + }); int responseStatus = httpClient.executeMethod(hostConfig, beginRequest, httpState); + checkResponseStatus("begin", responseStatus, beginRequest); //If we get here then we've received a 200 response //We're expecting the transfer id encoded in a JSON object... JSONObject response = new JSONObject(beginRequest.getResponseBodyAsString()); - String transferId = response.getString("transferId"); + + Transfer transfer = new Transfer(); + transfer.setTransferTarget(target); + + String transferId = response.getString(TransferCommons.PARAM_TRANSFER_ID); + transfer.setTransferId(transferId); + + if(response.has(TransferCommons.PARAM_VERSION_MAJOR)) + { + String versionMajor = response.getString(TransferCommons.PARAM_VERSION_MAJOR); + String versionMinor = response.getString(TransferCommons.PARAM_VERSION_MINOR); + String versionRevision = response.getString(TransferCommons.PARAM_VERSION_REVISION); + String edition = response.getString(TransferCommons.PARAM_VERSION_EDITION); + TransferVersion version = new TransferVersionImpl(versionMajor, versionMinor, versionRevision, edition); + transfer.setToVersion(version); + } + else + { + TransferVersion version = new TransferVersionImpl("0", "0", "0", "Unknown"); + transfer.setToVersion(version); + } + if(log.isDebugEnabled()) { log.debug("begin transfer transferId:" + transferId +", target:" + target); } - Transfer transfer = new Transfer(); - transfer.setTransferId(transferId); - transfer.setTransferTarget(target); + return transfer; } catch (RuntimeException e) { + log.debug("unexpected exception", e); throw e; } catch (Exception e) @@ -277,6 +304,7 @@ public class HttpClientTransmitterImpl implements TransferTransmitter } finally { + log.debug("releasing connection"); beginRequest.releaseConnection(); } } diff --git a/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImplTest.java b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImplTest.java index 15f41ed7e0..3cbc187946 100644 --- a/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImplTest.java +++ b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImplTest.java @@ -255,7 +255,7 @@ public class HttpClientTransmitterImplTest extends TestCase try { - transmitter.begin(target, "1234"); + transmitter.begin(target, "1234", new TransferVersionImpl("2", "2", "2", "Dummy")); fail(); } catch(TransferException ex) diff --git a/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java b/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java index 6bf978ad2b..354d415c29 100644 --- a/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java +++ b/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java @@ -81,6 +81,8 @@ import org.alfresco.service.cmr.transfer.TransferProgress.Status; 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; @@ -168,6 +170,7 @@ public class RepoTransferReceiverImpl implements TransferReceiver, 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"; @@ -188,6 +191,7 @@ public class RepoTransferReceiverImpl implements TransferReceiver, private DescriptorService descriptorService; private AlienProcessor alienProcessor; private JobLockService jobLockService; + private TransferVersionChecker transferVersionChecker; /** * Where the temporary files are stored. Tenant Domain Name, NodeRef @@ -247,6 +251,7 @@ public class RepoTransferReceiverImpl implements TransferReceiver, 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); @@ -399,7 +404,7 @@ public class RepoTransferReceiverImpl implements TransferReceiver, * * @see org.alfresco.repo.web.scripts.transfer.TransferReceiver#start() */ - public String start(String fromRepositoryId, boolean transferToSelf) + public String start(String fromRepositoryId, boolean transferToSelf, TransferVersion fromVersion) { log.debug("Start transfer"); @@ -408,6 +413,16 @@ public class RepoTransferReceiverImpl implements TransferReceiver, */ 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 */ @@ -485,6 +500,7 @@ public class RepoTransferReceiverImpl implements TransferReceiver, */ private NodeRef createTransferRecord() { + log.debug("Receiver createTransferRecord"); String tenantDomain = tenantService.getUserDomain(AuthenticationUtil.getRunAsUser()); NodeRef inboundTransferRecordsFolder = inboundTransferRecordsFolderMap.get(tenantDomain); @@ -1641,5 +1657,23 @@ public class RepoTransferReceiverImpl implements TransferReceiver, 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); } } diff --git a/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImplTest.java b/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImplTest.java index ab086a2907..cff694086d 100644 --- a/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImplTest.java +++ b/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImplTest.java @@ -118,7 +118,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest this.receiver = (RepoTransferReceiverImpl) this.getApplicationContext().getBean("transferReceiver"); this.policyComponent = (PolicyComponent) this.getApplicationContext().getBean("policyComponent"); this.searchService = (SearchService) this.getApplicationContext().getBean("searchService"); - this.dummyContent = "This is some dummy content."; + this.dummyContent = "This is some dummy content."; this.dummyContentBytes = dummyContent.getBytes("UTF-8"); setTransactionDefinition(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW)); authenticationComponent.setSystemUserAsCurrentUser(); @@ -198,7 +198,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest public Void execute() throws Throwable { log.debug("about to call start"); - String transferId = receiver.start("1234", true); + String transferId = receiver.start("1234", true, receiver.getVersion()); File stagingFolder = null; try { @@ -212,7 +212,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest Thread.sleep(1000); try { - receiver.start("1234", true); + receiver.start("1234", true, receiver.getVersion()); fail("Successfully started twice!"); } catch (TransferException ex) @@ -223,7 +223,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest Thread.sleep(300); try { - receiver.start("1234", true); + receiver.start("1234", true, receiver.getVersion()); fail("Successfully started twice!"); } catch (TransferException ex) @@ -297,7 +297,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest public Void execute() throws Throwable { log.debug("about to call start"); - String transferId = receiver.start("1234", true); + String transferId = receiver.start("1234", true, receiver.getVersion()); return null; } }; @@ -308,7 +308,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest public Void execute() throws Throwable { log.debug("about to call start"); - String transferId = receiver.start("1234", true); + String transferId = receiver.start("1234", true, receiver.getVersion()); Thread.sleep(1000); try { @@ -358,7 +358,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest startNewTransaction(); try { - String transferId = receiver.start("1234", true); + String transferId = receiver.start("1234", true, receiver.getVersion()); try { String contentId = "mytestcontent"; @@ -384,7 +384,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest startNewTransaction(); try { - String transferId = receiver.start("1234", true); + String transferId = receiver.start("1234", true, receiver.getVersion()); File snapshotFile = null; try { @@ -423,7 +423,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest try { - String transferId = receiver.start("1234", true); + String transferId = receiver.start("1234", true, receiver.getVersion()); try { node = createContentNode(transferId); @@ -480,7 +480,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest startNewTransaction(); try { - transferId = receiver.start("1234", true); + transferId = receiver.start("1234", true, receiver.getVersion()); node1 = createContentNode(transferId); nodes.add(node1); node2 = createContentNode(transferId); @@ -558,7 +558,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest setDefaultRollback(true); startNewTransaction(); - String transferId = receiver.start("1234", true); + String transferId = receiver.start("1234", true, receiver.getVersion()); List nodes = new ArrayList(); TransferManifestNormalNode node1 = createContentNode(transferId); @@ -639,7 +639,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest try { // Now delete nodes 8, 2, and 11 (11 and 2 are parent/child) - transferId = receiver.start("1234", true); + transferId = receiver.start("1234", true, receiver.getVersion()); String snapshot = createSnapshot(Arrays.asList(new TransferManifestNode[] { deletedNode8, deletedNode2, deletedNode11 })); log.debug(snapshot); @@ -715,7 +715,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest try { // try to restore node 2. Expect an "orphan" failure, since its parent (node11) is deleted - transferId = receiver.start("1234", true); + transferId = receiver.start("1234", true, receiver.getVersion()); String snapshot = createSnapshot(Arrays.asList(new TransferManifestNode[] { node2 })); log.debug(snapshot); receiver.saveSnapshot(transferId, new StringInputStream(snapshot, "UTF-8")); @@ -806,7 +806,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest { setDefaultRollback(true); startNewTransaction(); - String transferId = receiver.start("1234", true); + String transferId = receiver.start("1234", true, receiver.getVersion()); TransferManifestNormalNode node1 = createContentNode(transferId); TransferManifestNormalNode node2 = createContentNode(transferId); @@ -855,7 +855,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest startNewTransaction(); try { - transferId = receiver.start("1234", true); + transferId = receiver.start("1234", true, receiver.getVersion()); String snapshot = createSnapshot(Arrays.asList(new TransferManifestNode[] { deletedNode11 })); log.debug(snapshot); receiver.saveSnapshot(transferId, new StringInputStream(snapshot, "UTF-8")); @@ -898,7 +898,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest startNewTransaction(); try { - transferId = receiver.start("1234", true); + transferId = receiver.start("1234", true, receiver.getVersion()); String snapshot = createSnapshot(Arrays.asList(new TransferManifestNode[] { node2, node11 })); log.debug(snapshot); receiver.saveSnapshot(transferId, new StringInputStream(snapshot, "UTF-8")); @@ -925,7 +925,7 @@ public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest this.setDefaultRollback(false); startNewTransaction(); - final String transferId = receiver.start("1234", true); + final String transferId = receiver.start("1234", true, receiver.getVersion()); endTransaction(); startNewTransaction(); diff --git a/source/java/org/alfresco/repo/transfer/Transfer.java b/source/java/org/alfresco/repo/transfer/Transfer.java index f845459dda..8b96ac3a30 100644 --- a/source/java/org/alfresco/repo/transfer/Transfer.java +++ b/source/java/org/alfresco/repo/transfer/Transfer.java @@ -19,6 +19,7 @@ package org.alfresco.repo.transfer; import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.service.cmr.transfer.TransferVersion; /** * Information about a transfer which is in progress. @@ -29,6 +30,8 @@ public class Transfer { private String transferId; private TransferTarget transferTarget; + private TransferVersion toVersion; + public void setTransferId(String transferId) { @@ -86,4 +89,14 @@ public class Transfer { return "TransferId" + transferId + ", target:" + transferTarget ; } + + public void setToVersion(TransferVersion toVersion) + { + this.toVersion = toVersion; + } + + public TransferVersion getToVersion() + { + return toVersion; + } } diff --git a/source/java/org/alfresco/repo/transfer/TransferCommons.java b/source/java/org/alfresco/repo/transfer/TransferCommons.java index ca8a25e3e8..5db62c8cbf 100644 --- a/source/java/org/alfresco/repo/transfer/TransferCommons.java +++ b/source/java/org/alfresco/repo/transfer/TransferCommons.java @@ -42,6 +42,31 @@ public class TransferCommons */ public final static String PARAM_ALLOW_TRANSFER_TO_SELF = "allowTransferToSelf"; + /** + * TransferId + */ + public final static String PARAM_TRANSFER_ID = "transferId"; + + /** + * Major version + */ + public final static String PARAM_VERSION_MAJOR = "versionMajor"; + + /** + * Minor version + */ + public final static String PARAM_VERSION_MINOR = "versionMinor"; + + /** + * Revision version + */ + public final static String PARAM_VERSION_REVISION = "versionRevision"; + + /** + * Edition + */ + public final static String PARAM_VERSION_EDITION = "versionEdition"; + /** * Mapping between contentUrl and part name. * diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceCallbackTest.java b/source/java/org/alfresco/repo/transfer/TransferServiceCallbackTest.java index 2213d56a99..436f0303eb 100644 --- a/source/java/org/alfresco/repo/transfer/TransferServiceCallbackTest.java +++ b/source/java/org/alfresco/repo/transfer/TransferServiceCallbackTest.java @@ -65,6 +65,7 @@ import org.alfresco.service.cmr.transfer.TransferService2; import org.alfresco.service.cmr.transfer.TransferTarget; import org.alfresco.service.cmr.transfer.TransferEvent.TransferState; import org.alfresco.service.cmr.transfer.TransferProgress.Status; +import org.alfresco.service.cmr.transfer.TransferVersion; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; @@ -98,6 +99,7 @@ public class TransferServiceCallbackTest extends TestCase private String file1ContentUrl; private String file2ContentUrl; private String file3ContentUrl; + private TransferVersion version; private String localRepositoryId; @@ -121,6 +123,7 @@ public class TransferServiceCallbackTest extends TestCase fileFolderService = (FileFolderService) applicationContext.getBean("fileFolderService"); localRepositoryId = transferServiceImpl.getDescriptorService().getCurrentRepositoryDescriptor().getId(); + version = new TransferVersionImpl(transferServiceImpl.getDescriptorService().getServerDescriptor()); mockedTransferTransmitter = mock(TransferTransmitter.class); mockedCallback = mock(TransferCallback.class); @@ -139,6 +142,7 @@ public class TransferServiceCallbackTest extends TestCase transfer = new Transfer(); transfer.setTransferTarget(target); transfer.setTransferId(GUID.generate()); + transfer.setToVersion(version); companyHome = repository.getCompanyHome(); @@ -197,7 +201,7 @@ public class TransferServiceCallbackTest extends TestCase TransferProgress[] statuses = new TransferProgress[] {status0, status1, status2, status3, status4}; configureBasicMockTransmitter(statuses); - when(mockedTransferTransmitter.begin(target, localRepositoryId)).thenReturn(transfer); + when(mockedTransferTransmitter.begin(target, localRepositoryId, version)).thenReturn(transfer); TransferDefinition transferDef = new TransferDefinition(); transferDef.setNodes(folder1, file1, file2, file3); @@ -333,7 +337,7 @@ public class TransferServiceCallbackTest extends TestCase TransferProgress[] statuses = new TransferProgress[] {status0, status1, status2, status3, status4}; configureBasicMockTransmitter(statuses); - when(mockedTransferTransmitter.begin(target, localRepositoryId)).thenReturn(transfer); + when(mockedTransferTransmitter.begin(target, localRepositoryId, version)).thenReturn(transfer); TransferDefinition transferDef = new TransferDefinition(); transferDef.setNodes(folder1, file1, file2, file3); @@ -443,7 +447,7 @@ public class TransferServiceCallbackTest extends TestCase public void testTargetAlreadyLocked() { configureBasicMockTransmitter(null); - when(mockedTransferTransmitter.begin(target, "localRepositoryId")).thenThrow(new TransferException("Simulate lock unavailable")); + when(mockedTransferTransmitter.begin(target, "localRepositoryId", version)).thenThrow(new TransferException("Simulate lock unavailable")); TransferDefinition transferDef = new TransferDefinition(); transferDef.setNodes(folder1, file1, file2, file3); @@ -490,7 +494,7 @@ public class TransferServiceCallbackTest extends TestCase status0.setEndPosition(0); TransferProgress[] statuses = new TransferProgress[] {status0}; configureBasicMockTransmitter(statuses); - when(mockedTransferTransmitter.begin(target, localRepositoryId)).thenReturn(transfer); + when(mockedTransferTransmitter.begin(target, localRepositoryId, version)).thenReturn(transfer); doThrow(new TransferException("Simulate failure to write content")).when(mockedTransferTransmitter).sendManifest(any(Transfer.class), any(File.class), any(OutputStream.class)); when(mockedTransferTransmitter.getStatus(transfer)).thenReturn(statuses[0]); diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java b/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java index 05290e63f1..7b469f9fe8 100644 --- a/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java +++ b/source/java/org/alfresco/repo/transfer/TransferServiceImpl2.java @@ -89,6 +89,7 @@ import org.alfresco.service.cmr.transfer.TransferFailureException; import org.alfresco.service.cmr.transfer.TransferProgress; import org.alfresco.service.cmr.transfer.TransferService2; import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.service.cmr.transfer.TransferVersion; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.service.namespace.QName; @@ -127,6 +128,7 @@ public class TransferServiceImpl2 implements TransferService2 private static final String MSG_TARGET_ERROR = "transfer_service.target_error"; private static final String MSG_UNKNOWN_TARGET_ERROR = "transfer_service.unknown_target_error"; private static final String MSG_TARGET_NOT_ENABLED = "transfer_service.target_not_enabled"; + private static final String MSG_INCOMPATIBLE_VERSIONS = "transfer_service.incompatible_versions"; private static final String FILE_DIRECTORY = "transfer"; private static final String FILE_SUFFIX = ".xml"; @@ -149,6 +151,7 @@ public class TransferServiceImpl2 implements TransferService2 PropertyCheck.mandatory(this, "actionService", actionService); PropertyCheck.mandatory(this, "transactionService", transactionService); PropertyCheck.mandatory(this, "descriptorService", descriptorService); + PropertyCheck.mandatory(this, "transferVersionChecker", getTransferVersionChecker()); } private String transferSpaceQuery; @@ -162,6 +165,7 @@ public class TransferServiceImpl2 implements TransferService2 private TransferReporter transferReporter; private TenantService tenantService; private DescriptorService descriptorService; + private TransferVersionChecker transferVersionChecker; /** * How long to delay while polling for commit status. @@ -552,7 +556,11 @@ public class TransferServiceImpl2 implements TransferService2 int pollPosition = -1; boolean cancelled = false; - final String localRepositoryId = getDescriptorService().getCurrentRepositoryDescriptor().getId(); + + Descriptor currentDescriptor = descriptorService.getCurrentRepositoryDescriptor(); + Descriptor serverDescriptor = descriptorService.getServerDescriptor(); + final String localRepositoryId = currentDescriptor.getId(); + TransferVersion fromVersion = new TransferVersionImpl(serverDescriptor); // Wire in the transferReport - so any callbacks are stored in transferReport TransferCallback reportCallback = new TransferCallback() @@ -577,11 +585,12 @@ public class TransferServiceImpl2 implements TransferService2 case Begin: { eventProcessor.start(); - manifest = createManifest(definition); + + manifest = createManifest(definition, localRepositoryId, fromVersion); logger.debug("transfer begin"); target = getTransferTarget(targetName); checkTargetEnabled(target); - transfer = transmitter.begin(target, localRepositoryId); + transfer = transmitter.begin(target, localRepositoryId, fromVersion); String transferId = transfer.getTransferId(); TransferStatus status = new TransferStatus(); transferMonitoring.put(transferId, status); @@ -596,6 +605,13 @@ public class TransferServiceImpl2 implements TransferService2 case Prepare: { + // check alfresco versions are compatible + TransferVersion toVersion = transfer.getToVersion(); + if(!getTransferVersionChecker().checkTransferVersions(fromVersion, toVersion)) + { + throw new TransferException(MSG_INCOMPATIBLE_VERSIONS, new Object[] {transfer.getTransferId(), fromVersion, toVersion}); + } + // send Manifest, get the requsite back. eventProcessor.sendSnapshot(1,1); @@ -900,7 +916,7 @@ public class TransferServiceImpl2 implements TransferService2 } } - private File createManifest(TransferDefinition definition) + private File createManifest(TransferDefinition definition, String repositoryId, TransferVersion fromVersion) throws IOException, SAXException { // which nodes to write to the snapshot @@ -925,8 +941,8 @@ public class TransferServiceImpl2 implements TransferService2 // Write the manifest file TransferManifestWriter formatter = new XMLTransferManifestWriter(); TransferManifestHeader header = new TransferManifestHeader(); - Descriptor descriptor = descriptorService.getCurrentRepositoryDescriptor(); - header.setRepositoryId(descriptor.getId()); + header.setRepositoryId(repositoryId); + header.setTransferVersion(fromVersion); header.setCreatedDate(new Date()); header.setNodeCount(nodes.size()); header.setSync(definition.isSync()); @@ -1449,10 +1465,20 @@ public class TransferServiceImpl2 implements TransferService2 return descriptorService; } + public void setTransferVersionChecker(TransferVersionChecker transferVersionChecker) + { + this.transferVersionChecker = transferVersionChecker; + } + + public TransferVersionChecker getTransferVersionChecker() + { + return transferVersionChecker; + } + private class TransferStatus { boolean cancelMe = false; boolean cancelInProgress = false; } - -} + + } diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java b/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java index 960755ede4..8f941ea154 100644 --- a/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java +++ b/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java @@ -118,6 +118,7 @@ public class TransferServiceImplTest extends BaseAlfrescoSpringTest private PersonService personService; private DescriptorService descriptorService; private CopyService copyService; + private Descriptor serverDescriptor; String COMPANY_HOME_XPATH_QUERY = "/{http://www.alfresco.org/model/application/1.0}company_home"; String GUEST_HOME_XPATH_QUERY = "/{http://www.alfresco.org/model/application/1.0}company_home/{http://www.alfresco.org/model/application/1.0}guest_home"; @@ -153,12 +154,21 @@ public class TransferServiceImplTest extends BaseAlfrescoSpringTest this.descriptorService = (DescriptorService)this.applicationContext.getBean("DescriptorService"); this.copyService = (CopyService)this.applicationContext.getBean("CopyService"); + this.serverDescriptor = descriptorService.getServerDescriptor(); + REPO_ID_B = descriptorService.getCurrentRepositoryDescriptor().getId(); authenticationComponent.setSystemUserAsCurrentUser(); assertNotNull("receiver is null", this.receiver); } + @Override + public void runBare() throws Throwable + { + preventTransaction(); + super.runBare(); + } + /** * Test create target. * @@ -3234,31 +3244,14 @@ public class TransferServiceImplTest extends BaseAlfrescoSpringTest TransferTarget target = transferService.createAndSaveTransferTarget(name, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); return target; } - - private void createUser(String userName, String password) - { - if (this.authenticationService.authenticationExists(userName) == false) - { - this.authenticationService.createAuthentication(userName, password.toCharArray()); - - PropertyMap ppOne = new PropertyMap(4); - ppOne.put(ContentModel.PROP_USERNAME, userName); - ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName"); - ppOne.put(ContentModel.PROP_LASTNAME, "lastName"); - ppOne.put(ContentModel.PROP_EMAIL, "email@email.com"); - ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle"); - - this.personService.createPerson(ppOne); - } - } - + private DescriptorService getMockDescriptorService(String repositoryId) { DescriptorService descriptorService = mock(DescriptorService.class); Descriptor descriptor = mock(Descriptor.class); - when(descriptor.getId()).thenReturn(repositoryId); when(descriptorService.getCurrentRepositoryDescriptor()).thenReturn(descriptor); + when(descriptorService.getServerDescriptor()).thenReturn(serverDescriptor); return descriptorService; } diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceToBeRefactoredTest.java b/source/java/org/alfresco/repo/transfer/TransferServiceToBeRefactoredTest.java index 6d0bc39142..9a2d2ec459 100644 --- a/source/java/org/alfresco/repo/transfer/TransferServiceToBeRefactoredTest.java +++ b/source/java/org/alfresco/repo/transfer/TransferServiceToBeRefactoredTest.java @@ -45,6 +45,7 @@ import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transfer.manifest.TransferManifestNodeFactory; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.lock.LockService; @@ -111,6 +112,7 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest private PersonService personService; private DescriptorService descriptorService; private CopyService copyService; + private Descriptor serverDescriptor; String COMPANY_HOME_XPATH_QUERY = "/{http://www.alfresco.org/model/application/1.0}company_home"; String GUEST_HOME_XPATH_QUERY = "/{http://www.alfresco.org/model/application/1.0}company_home/{http://www.alfresco.org/model/application/1.0}guest_home"; @@ -157,6 +159,7 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest this.personService = (PersonService)this.applicationContext.getBean("PersonService"); this.descriptorService = (DescriptorService)this.applicationContext.getBean("DescriptorService"); this.copyService = (CopyService)this.applicationContext.getBean("CopyService"); + this.serverDescriptor = descriptorService.getServerDescriptor(); REPO_ID_B = descriptorService.getCurrentRepositoryDescriptor().getId(); @@ -180,7 +183,7 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest String guestHomeQuery = "/app:company_home/app:guest_home"; ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); assertEquals("", 1, guestHomeResult.length()); - NodeRef guestHome = guestHomeResult.getNodeRef(0); + final NodeRef guestHome = guestHomeResult.getNodeRef(0); /** * For unit test @@ -189,8 +192,8 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(this.receiver, this.contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); @@ -202,70 +205,73 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest * Now go ahead and create our first transfer target * This needs to be committed before we can call transfer asycnc. */ - String CONTENT_TITLE = "ContentTitle"; - String CONTENT_NAME_A = "Report Node A"; - String CONTENT_NAME_B = "Report Node B"; - Locale CONTENT_LOCALE = Locale.GERMAN; - String CONTENT_STRING = "Hello"; - - NodeRef nodeRefA = null; - NodeRef nodeRefB = null; - NodeRef testFolder = null; - - String targetName = "testTransferReport"; - - startNewTransaction(); - try + final String CONTENT_TITLE = "ContentTitle"; + final String CONTENT_NAME_A = "Report Node A"; + final String CONTENT_NAME_B = "Report Node B"; + final Locale CONTENT_LOCALE = Locale.GERMAN; + final String CONTENT_STRING = "Hello"; + + class TestData { + NodeRef nodeRefA; + NodeRef nodeRefB; + NodeRef testFolder; + NodeRef transferReport; + NodeRef transferDestReport; + } + final TestData testData = new TestData(); + + final String targetName = "testTransferReport"; + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable { - String name = GUID.generate(); - ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); - testFolder = child.getChildRef(); - nodeService.setProperty(testFolder, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(testFolder, ContentModel.PROP_NAME, name); - } - - { + { + String name = GUID.generate(); + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); + testData.testFolder = child.getChildRef(); + nodeService.setProperty(testData.testFolder, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.testFolder, ContentModel.PROP_NAME, name); + } + + { + /** + * Create a test node that we will read and write + */ + ChildAssociationRef child = nodeService.createNode(testData.testFolder, ContentModel.ASSOC_CONTAINS, QName.createQName(GUID.generate()), ContentModel.TYPE_CONTENT); + testData.nodeRefA = child.getChildRef(); + nodeService.setProperty(testData.nodeRefA, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.nodeRefA, ContentModel.PROP_NAME, CONTENT_NAME_A); + + ContentWriter writer = contentService.getWriter(testData.nodeRefA, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + + { + ChildAssociationRef child = nodeService.createNode(testData.testFolder, ContentModel.ASSOC_CONTAINS, QName.createQName(GUID.generate()), ContentModel.TYPE_CONTENT); + testData.nodeRefB = child.getChildRef(); + nodeService.setProperty(testData.nodeRefB, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.nodeRefB, ContentModel.PROP_NAME, CONTENT_NAME_B); + + ContentWriter writer = contentService.getWriter(testData.nodeRefB, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + /** - * Create a test node that we will read and write + * Now go ahead and create our first transfer target */ - ChildAssociationRef child = nodeService.createNode(testFolder, ContentModel.ASSOC_CONTAINS, QName.createQName(GUID.generate()), ContentModel.TYPE_CONTENT); - nodeRefA = child.getChildRef(); - nodeService.setProperty(nodeRefA, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(nodeRefA, ContentModel.PROP_NAME, CONTENT_NAME_A); - - ContentWriter writer = contentService.getWriter(nodeRefA, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); + if (!transferService.targetExists(targetName)) + { + createTransferTarget(targetName); + } + return null; } - - { - ChildAssociationRef child = nodeService.createNode(testFolder, ContentModel.ASSOC_CONTAINS, QName.createQName(GUID.generate()), ContentModel.TYPE_CONTENT); - nodeRefB = child.getChildRef(); - nodeService.setProperty(nodeRefB, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(nodeRefB, ContentModel.PROP_NAME, CONTENT_NAME_B); - - ContentWriter writer = contentService.getWriter(nodeRefB, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - - /** - * Now go ahead and create our first transfer target - */ - if(!transferService.targetExists(targetName)) - { - createTransferTarget(targetName); - } - } - finally - { - endTransaction(); - } - - NodeRef transferReport = null; - NodeRef transferDestReport = null; - + }); + /** * Step 1. * Call the transfer method. to get a failed transfer - orphan nodes exist @@ -274,305 +280,302 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest startNewTransaction(); try { - TestTransferCallback callback = new TestTransferCallback(); - Set callbacks = new HashSet(); - callbacks.add(callback); - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(nodeRefA); - nodes.add(nodeRefB); - // missing the folder node (testFolder) - definition.setNodes(nodes); - - // Do the transfer here - - try - { - transferService.transfer(targetName, definition, callbacks); - fail("transfer should have failed with an orphan not found exception"); - } - catch (TransferException te) - { - logger.debug("deliberatly caught and ignored exception"); - } - - // Can't dirty read transfer report here - - boolean foundSourceReport = false; - boolean foundDestReport = false; - - for(TransferEvent event : callback.getEvents()) - { - if(event instanceof TransferEventReport) - { - TransferEventReport reportEvent = (TransferEventReport)event; - switch (reportEvent.getReportType()) - { - case DESTINATION: - foundDestReport = true; - transferDestReport = reportEvent.getNodeRef(); - assertNotNull("dest transfer nodeId null", transferDestReport); - break; - - case SOURCE: - foundSourceReport = true; - transferReport = reportEvent.getNodeRef(); - break; - } - } - } - - assertTrue("source report not found", foundSourceReport); - assertTrue("dest report not found", foundDestReport); - } - finally - { - endTransaction(); - } - - setDefaultRollback(false); - - /** - * Now validate the client side error transfer report against the xsd file - */ - startNewTransaction(); - try - { - ContentReader reader = contentService.getReader(transferReport, ContentModel.PROP_CONTENT); - assertNotNull("transfer reader is null", reader); - assertEquals("client report mimetype not set", reader.getMimetype(), MimetypeMap.MIMETYPE_XML); - String name = (String)nodeService.getProperty(transferReport, ContentModel.PROP_NAME); - assertTrue("client report does not end with .xml", name.endsWith(".xml")); - - logger.debug("This report should have failed"); - if(logger.isDebugEnabled()) - { - dumpToSystemOut(transferReport); - } - - // Now validate the client side transfer report against the XSD - Source transferReportSource = new StreamSource(reader.getContentInputStream()); - SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - final String TRANSFER_REPORT_SCHEMA_LOCATION = "classpath:org/alfresco/repo/transfer/report/TransferReport2.xsd"; - Schema schema = sf.newSchema(ResourceUtils.getURL(TRANSFER_REPORT_SCHEMA_LOCATION)); - Validator validator = schema.newValidator(); - try - { - validator.validate(transferReportSource); - } - catch (Exception e) - { - fail(e.getMessage() ); - } - } - finally - { - endTransaction(); - } - + TestTransferCallback callback = new TestTransferCallback(); + Set callbacks = new HashSet(); + callbacks.add(callback); + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.nodeRefA); + nodes.add(testData.nodeRefB); + // missing the folder node (testFolder) + definition.setNodes(nodes); + + // Do the transfer here + + try + { + transferService.transfer(targetName, definition, callbacks); + fail("transfer should have failed with an orphan not found exception"); + } + catch (TransferException te) + { + logger.debug("deliberatly caught and ignored exception"); + } + + // Can't dirty read transfer report here + + boolean foundSourceReport = false; + boolean foundDestReport = false; + + for (TransferEvent event : callback.getEvents()) + { + if (event instanceof TransferEventReport) + { + TransferEventReport reportEvent = (TransferEventReport) event; + switch (reportEvent.getReportType()) + { + case DESTINATION: + foundDestReport = true; + testData.transferDestReport = reportEvent.getNodeRef(); + assertNotNull("dest transfer nodeId null", testData.transferDestReport); + break; + + case SOURCE: + foundSourceReport = true; + testData.transferReport = reportEvent.getNodeRef(); + break; + } + } + } + + assertTrue("source report not found", foundSourceReport); + assertTrue("dest report not found", foundDestReport); + } + finally + { + endTransaction(); + } + + setDefaultRollback(false); + + /** + * Now validate the client side error transfer report against the xsd file + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + ContentReader reader = contentService.getReader(testData.transferReport, ContentModel.PROP_CONTENT); + assertNotNull("transfer reader is null", reader); + assertEquals("client report mimetype not set", reader.getMimetype(), MimetypeMap.MIMETYPE_XML); + String name = (String) nodeService.getProperty(testData.transferReport, ContentModel.PROP_NAME); + assertTrue("client report does not end with .xml", name.endsWith(".xml")); + + logger.debug("This report should have failed"); + if (logger.isDebugEnabled()) + { + dumpToSystemOut(testData.transferReport); + } + + // Now validate the client side transfer report against the XSD + Source transferReportSource = new StreamSource(reader.getContentInputStream()); + SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + final String TRANSFER_REPORT_SCHEMA_LOCATION = "classpath:org/alfresco/repo/transfer/report/TransferReport2.xsd"; + Schema schema = sf.newSchema(ResourceUtils.getURL(TRANSFER_REPORT_SCHEMA_LOCATION)); + Validator validator = schema.newValidator(); + try + { + validator.validate(transferReportSource); + } + catch (Exception e) + { + fail(e.getMessage()); + } + return null; + } + }); + /** * Step 2 * Call the transfer method to get a good success transfer report */ - startNewTransaction(); - try - { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable { - TestTransferCallback callback = new TestTransferCallback(); - Set callbacks = new HashSet(); - callbacks.add(callback); - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(nodeRefA); - nodes.add(nodeRefB); - nodes.add(testFolder); - definition.setNodes(nodes); - - transferReport = transferService.transfer(targetName, definition, callbacks); - assertNotNull("transfer report is null", transferReport); - // Can't dirty read transfer report here - - boolean foundSourceReport = false; - boolean foundDestReport = false; - - for(TransferEvent event : callback.getEvents()) { - if(event instanceof TransferEventReport) + TestTransferCallback callback = new TestTransferCallback(); + Set callbacks = new HashSet(); + callbacks.add(callback); + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.nodeRefA); + nodes.add(testData.nodeRefB); + nodes.add(testData.testFolder); + definition.setNodes(nodes); + + testData.transferReport = transferService.transfer(targetName, definition, callbacks); + assertNotNull("transfer report is null", testData.transferReport); + // Can't dirty read transfer report here + + boolean foundSourceReport = false; + boolean foundDestReport = false; + + for (TransferEvent event : callback.getEvents()) { - TransferEventReport reportEvent = (TransferEventReport)event; - switch (reportEvent.getReportType()) + if (event instanceof TransferEventReport) { + TransferEventReport reportEvent = (TransferEventReport) event; + switch (reportEvent.getReportType()) + { case DESTINATION: foundDestReport = true; - transferDestReport = reportEvent.getNodeRef(); - assertNotNull("dest transfer nodeId null", transferDestReport); - assertFalse("dest transfer nodeId not correct", transferReport.equals(transferDestReport)); + testData.transferDestReport = reportEvent.getNodeRef(); + assertNotNull("dest transfer nodeId null", testData.transferDestReport); + assertFalse("dest transfer nodeId not correct", testData.transferReport.equals(testData.transferDestReport)); break; - + case SOURCE: foundSourceReport = true; - - assertEquals("source transfer nodeId not correct", transferReport, reportEvent.getNodeRef()); - break; + + assertEquals("source transfer nodeId not correct", testData.transferReport, reportEvent.getNodeRef()); + break; + } } } + + assertTrue("source report not found", foundSourceReport); + assertTrue("dest report not found", foundDestReport); } - - assertTrue("source report not found", foundSourceReport); - assertTrue("dest report not found", foundDestReport); + return null; } - } - finally - { - endTransaction(); - } - + }); + /** * Now validate the client side transfer report against the xsd file */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - ContentReader reader = contentService.getReader(transferReport, ContentModel.PROP_CONTENT); - assertNotNull("transfer reader is null", reader); - - logger.debug("This report should succeed"); - if(logger.isDebugEnabled()) + @Override + public Void execute() throws Throwable { - dumpToSystemOut(transferReport); + ContentReader reader = contentService.getReader(testData.transferReport, ContentModel.PROP_CONTENT); + assertNotNull("transfer reader is null", reader); + + logger.debug("This report should succeed"); + if (logger.isDebugEnabled()) + { + dumpToSystemOut(testData.transferReport); + } + + // Now validate the client side transfer report against the XSD + Source transferReportSource = new StreamSource(reader.getContentInputStream()); + SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + final String TRANSFER_REPORT_SCHEMA_LOCATION = "classpath:org/alfresco/repo/transfer/report/TransferReport2.xsd"; + Schema schema = sf.newSchema(ResourceUtils.getURL(TRANSFER_REPORT_SCHEMA_LOCATION)); + Validator validator = schema.newValidator(); + try + { + validator.validate(transferReportSource); + } + catch (Exception e) + { + fail(e.getMessage()); + } + return null; } - - // Now validate the client side transfer report against the XSD - Source transferReportSource = new StreamSource(reader.getContentInputStream()); - SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - final String TRANSFER_REPORT_SCHEMA_LOCATION = "classpath:org/alfresco/repo/transfer/report/TransferReport2.xsd"; - Schema schema = sf.newSchema(ResourceUtils.getURL(TRANSFER_REPORT_SCHEMA_LOCATION)); - Validator validator = schema.newValidator(); - try - { - validator.validate(transferReportSource); - } - catch (Exception e) - { - fail(e.getMessage() ); - } - } - finally - { - endTransaction(); - } - + }); /** * Now validate the destination side transfer report against its xsd file */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - ContentReader reader = contentService.getReader(transferDestReport, ContentModel.PROP_CONTENT); - assertNotNull("transfer reader is null", reader); - - assertEquals("dest report mimetype not set", reader.getMimetype(), MimetypeMap.MIMETYPE_XML); - String name = (String)nodeService.getProperty(transferReport, ContentModel.PROP_NAME); - assertTrue("dest report does not end with .xml", name.endsWith(".xml")); - - if(logger.isDebugEnabled()) + @Override + public Void execute() throws Throwable { - dumpToSystemOut(transferDestReport); + ContentReader reader = contentService.getReader(testData.transferDestReport, ContentModel.PROP_CONTENT); + assertNotNull("transfer reader is null", reader); + + assertEquals("dest report mimetype not set", reader.getMimetype(), MimetypeMap.MIMETYPE_XML); + String name = (String) nodeService.getProperty(testData.transferReport, ContentModel.PROP_NAME); + assertTrue("dest report does not end with .xml", name.endsWith(".xml")); + + if (logger.isDebugEnabled()) + { + dumpToSystemOut(testData.transferDestReport); + } + + // Now validate the destination side transfer report against the XSD + Source transferReportSource = new StreamSource(reader.getContentInputStream()); + SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + final String TRANSFER_REPORT_SCHEMA_LOCATION = "classpath:org/alfresco/repo/transfer/reportd/TransferDestinationReport.xsd"; + Schema schema = sf.newSchema(ResourceUtils.getURL(TRANSFER_REPORT_SCHEMA_LOCATION)); + Validator validator = schema.newValidator(); + try + { + validator.validate(transferReportSource); + } + catch (Exception e) + { + fail("Destination Transfer Report " + e.getMessage()); + } + return null; } - - // Now validate the destination side transfer report against the XSD - Source transferReportSource = new StreamSource(reader.getContentInputStream()); - SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - final String TRANSFER_REPORT_SCHEMA_LOCATION = "classpath:org/alfresco/repo/transfer/reportd/TransferDestinationReport.xsd"; - Schema schema = sf.newSchema(ResourceUtils.getURL(TRANSFER_REPORT_SCHEMA_LOCATION)); - Validator validator = schema.newValidator(); - try - { - validator.validate(transferReportSource); - } - catch (Exception e) - { - fail("Destination Transfer Report " + e.getMessage() ); - } - } - finally - { - endTransaction(); - } - + }); /** * Now validate all transfer reports. */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - - String query = "TYPE:\"trx:transferReportDest\""; - ResultSet results = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_LUCENE, query); - - SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - final String TRANSFER_REPORT_SCHEMA_LOCATION = "classpath:org/alfresco/repo/transfer/reportd/TransferDestinationReport.xsd"; - Schema schema = sf.newSchema(ResourceUtils.getURL(TRANSFER_REPORT_SCHEMA_LOCATION)); - Validator validator = schema.newValidator(); - - for(ResultSetRow result : results) + @Override + public Void execute() throws Throwable { - NodeRef reportNode = result.getNodeRef(); - - logger.debug("validating reportNode " + reportNode); - // Now validate the destination side transfer report against the XSD - ContentReader reader = contentService.getReader(reportNode, ContentModel.PROP_CONTENT); - assertNotNull("transfer reader is null", reader); - if (reader.getMimetype().equals(MimetypeMap.MIMETYPE_XML)) + + String query = "TYPE:\"trx:transferReportDest\""; + ResultSet results = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_LUCENE, query); + + SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + final String TRANSFER_REPORT_SCHEMA_LOCATION = "classpath:org/alfresco/repo/transfer/reportd/TransferDestinationReport.xsd"; + Schema schema = sf.newSchema(ResourceUtils.getURL(TRANSFER_REPORT_SCHEMA_LOCATION)); + Validator validator = schema.newValidator(); + + for (ResultSetRow result : results) { - Source transferReportSource = new StreamSource(reader.getContentInputStream()); - try + NodeRef reportNode = result.getNodeRef(); + + logger.debug("validating reportNode " + reportNode); + // Now validate the destination side transfer report against the XSD + ContentReader reader = contentService.getReader(reportNode, ContentModel.PROP_CONTENT); + assertNotNull("transfer reader is null", reader); + if (reader.getMimetype().equals(MimetypeMap.MIMETYPE_XML)) { - validator.validate(transferReportSource); - } - catch (Exception e) - { - fail("Destination Transfer Report reportNode:" + reportNode + " message :" + e.getMessage() ); + Source transferReportSource = new StreamSource(reader.getContentInputStream()); + try + { + validator.validate(transferReportSource); + } + catch (Exception e) + { + fail("Destination Transfer Report reportNode:" + reportNode + " message :" + e.getMessage()); + } } } + return null; } - } - finally + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try - { - - logger.debug("now delete the target:" + targetName); - - transferService.deleteTransferTarget(targetName); - } - finally - { - endTransaction(); - } - } // test transfer report + @Override + public Void execute() throws Throwable + { + + logger.debug("now delete the target:" + targetName); + + transferService.deleteTransferTarget(targetName); + return null; + } + }); + } // test transfer report private void dumpToSystemOut(NodeRef nodeRef) throws IOException { ContentReader reader2 = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); assertNotNull("transfer reader is null", reader2); InputStream is = reader2.getContentInputStream(); - + BufferedReader br = new BufferedReader(new InputStreamReader(is)); - + String s = br.readLine(); - while(s != null) + while (s != null) { - System.out.println(s); + System.out.println(s); s = br.readLine(); } } - private UnitTestTransferManifestNodeFactory unitTestKludgeToTransferGuestHomeToCompanyHome() + private UnitTestTransferManifestNodeFactory unitTestKludgeToTransferGuestHomeToCompanyHome() { /** * For unit test @@ -581,20 +584,19 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(this.receiver, this.contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); - + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_A); transferServiceImpl.setDescriptorService(mockedDescriptorService); - + return testNodeFactory; } - - + /** * Test the transfer method behaviour with respect to sync folders - sending a complete set * of nodes and implying delete from the absence of an association. @@ -636,11 +638,11 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest public void testTransferSyncNodes() throws Exception { setDefaultRollback(false); - - String CONTENT_TITLE = "ContentTitle"; - String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; - Locale CONTENT_LOCALE = Locale.GERMAN; - String CONTENT_STRING = "Hello"; + + final String CONTENT_TITLE = "ContentTitle"; + final String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + final Locale CONTENT_LOCALE = Locale.GERMAN; + final String CONTENT_STRING = "Hello"; /** * For unit test @@ -649,459 +651,450 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); - + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_A); transferServiceImpl.setDescriptorService(mockedDescriptorService); - - /** - * Now go ahead and create our first transfer target - */ - String targetName = "testTransferSyncNodes"; - TransferTarget transferMe; - NodeRef A1NodeRef; - NodeRef A2NodeRef; - NodeRef A3NodeRef; - NodeRef A4NodeRef; - NodeRef A5NodeRef; - - NodeRef destNodeRef; - - startNewTransaction(); - try - { - /** - * Get guest home - */ - String guestHomeQuery = "/app:company_home/app:guest_home"; - ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); - assertEquals("", 1, guestHomeResult.length()); - NodeRef guestHome = guestHomeResult.getNodeRef(0); - - /** - * Create a test nodes A1 through A5 that we will read and write - */ - { - // Node A1 - String name = GUID.generate(); - ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); - A1NodeRef = child.getChildRef(); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_NAME, name); - } - - { - // Node A2 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_FOLDER); - A2NodeRef = child.getChildRef(); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_NAME, "A2"); - } - - { - // Node A3 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_CONTENT); - A3NodeRef = child.getChildRef(); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_NAME, "A3"); - - ContentWriter writer = contentService.getWriter(A3NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - { - // Node A4 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A4"), ContentModel.TYPE_CONTENT); - A4NodeRef = child.getChildRef(); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_NAME, "A4"); - - ContentWriter writer = contentService.getWriter(A4NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - { - // Node A5 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_CONTENT); - A5NodeRef = child.getChildRef(); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_NAME, "A5"); - - ContentWriter writer = contentService.getWriter(A5NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - - // Create the transfer target if it does not already exist - if(!transferService.targetExists(targetName)) - { - transferMe = createTransferTarget(targetName); - } - else - { - transferMe = transferService.getTransferTarget(targetName); - } - } - finally - { - endTransaction(); - } - /** - * Step 1. Add Node A1. + * Now go ahead and create our first transfer target */ - startNewTransaction(); - try + final String targetName = "testTransferSyncNodes"; + class TestData { - /** - * Transfer Node A with no children - */ - { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally - { - endTransaction(); + TransferTarget transferMe; + NodeRef A1NodeRef; + NodeRef A2NodeRef; + NodeRef A3NodeRef; + NodeRef A4NodeRef; + NodeRef A5NodeRef; + NodeRef destNodeRef; } - startNewTransaction(); - try + final TestData testData = new TestData(); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - // Now validate that the target node exists and has similar properties to the source - destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref does not exist", nodeService.exists(destNodeRef)); - assertEquals("title is wrong", (String)nodeService.getProperty(destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE); - assertEquals("type is wrong", nodeService.getType(A1NodeRef), nodeService.getType(destNodeRef)); - - // Check injected transferred aspect. - assertNotNull("transferredAspect", (String)nodeService.getProperty(destNodeRef, TransferModel.PROP_REPOSITORY_ID)); - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + /** + * Create a test nodes A1 through A5 that we will read and write + */ + { + // Node A1 + String name = GUID.generate(); + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); + testData.A1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_NAME, name); + } + + { + // Node A2 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_FOLDER); + testData.A2NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_NAME, "A2"); + } + + { + // Node A3 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_CONTENT); + testData.A3NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_NAME, "A3"); + + ContentWriter writer = contentService.getWriter(testData.A3NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + { + // Node A4 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A4"), ContentModel.TYPE_CONTENT); + testData.A4NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_NAME, "A4"); + + ContentWriter writer = contentService.getWriter(testData.A4NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + { + // Node A5 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_CONTENT); + testData.A5NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_NAME, "A5"); + + ContentWriter writer = contentService.getWriter(testData.A5NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + + // Create the transfer target if it does not already exist + if (!transferService.targetExists(targetName)) + { + testData.transferMe = createTransferTarget(targetName); + } + else + { + testData.transferMe = transferService.getTransferTarget(targetName); + } + return null; + } + }); + /** - * Step 2. Add Node A2, A3, A4, A5. + * Step 1. Add Node A1. */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - /** - * Transfer Node A 1-5 - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - nodes.add(A2NodeRef); - nodes.add(A3NodeRef); - nodes.add(A4NodeRef); - nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally - { - endTransaction(); - } + /** + * Transfer Node A with no children + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - // Now validate that the target node exists and has similar properties to the source - destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A1NodeRef))); - assertTrue("dest node ref A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A2NodeRef))); - assertTrue("dest node ref A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertTrue("dest node ref A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A4NodeRef))); - assertTrue("dest node ref A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - - // Check injected transferred aspect. - assertNotNull("transferredAspect", (String)nodeService.getProperty(destNodeRef, TransferModel.PROP_REPOSITORY_ID)); - } - finally + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref does not exist", nodeService.exists(testData.destNodeRef)); + assertEquals("title is wrong", (String) nodeService.getProperty(testData.destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE); + assertEquals("type is wrong", nodeService.getType(testData.A1NodeRef), nodeService.getType(testData.destNodeRef)); + + // Check injected transferred aspect. + assertNotNull("transferredAspect", (String) nodeService.getProperty(testData.destNodeRef, TransferModel.PROP_REPOSITORY_ID)); + return null; + } + }); + /** + * Step 2. Add Node A2, A3, A4, A5. + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + /** + * Transfer Node A 1-5 + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + nodes.add(testData.A2NodeRef); + nodes.add(testData.A3NodeRef); + nodes.add(testData.A4NodeRef); + nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A1NodeRef))); + assertTrue("dest node ref A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A2NodeRef))); + assertTrue("dest node ref A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertTrue("dest node ref A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A4NodeRef))); + assertTrue("dest node ref A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + + // Check injected transferred aspect. + assertNotNull("transferredAspect", (String) nodeService.getProperty(testData.destNodeRef, TransferModel.PROP_REPOSITORY_ID)); + return null; + } + }); /** * Step 3 - remove folder node A2 */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - nodeService.deleteNode(A2NodeRef); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - /** - * Transfer Node A 1-5 - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - //nodes.add(A2NodeRef); - nodes.add(A3NodeRef); - //nodes.add(A4NodeRef); - //nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally + nodeService.deleteNode(testData.A2NodeRef); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } + @Override + public Void execute() throws Throwable + { + /** + * Transfer Node A 1-5 + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + // nodes.add(A2NodeRef); + nodes.add(testData.A3NodeRef); + // nodes.add(A4NodeRef); + // nodes.add(A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A1NodeRef))); + assertFalse("dest node ref A2 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A2NodeRef))); + assertTrue("dest node ref A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertFalse("dest node ref A4 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A4NodeRef))); + assertFalse("dest node ref A5 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A1NodeRef))); - assertFalse("dest node ref A2 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A2NodeRef))); - assertTrue("dest node ref A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertFalse("dest node ref A4 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A4NodeRef))); - assertFalse("dest node ref A5 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - - // Check injected transferred aspect. - assertNotNull("transferredAspect", (String)nodeService.getProperty(destNodeRef, TransferModel.PROP_REPOSITORY_ID)); - } - finally - { - endTransaction(); - } - + // Check injected transferred aspect. + assertNotNull("transferredAspect", (String) nodeService.getProperty(testData.destNodeRef, TransferModel.PROP_REPOSITORY_ID)); + return null; + } + }); /** * Step 4 - remove content node A3 */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - nodeService.deleteNode(A3NodeRef); - } - finally - { - endTransaction(); - } - startNewTransaction(); - try - { - /** - * Transfer Node A 1-5 - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - //nodes.add(A2NodeRef); - //nodes.add(A3NodeRef); - //nodes.add(A4NodeRef); - //nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally + nodeService.deleteNode(testData.A3NodeRef); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } + @Override + public Void execute() throws Throwable + { + /** + * Transfer Node A 1-5 + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + // nodes.add(A2NodeRef); + // nodes.add(A3NodeRef); + // nodes.add(A4NodeRef); + // nodes.add(A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A1NodeRef))); + assertFalse("dest node ref A2 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A2NodeRef))); + assertFalse("dest node ref A3 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertFalse("dest node ref A4 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A4NodeRef))); + assertFalse("dest node ref A5 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A1NodeRef))); - assertFalse("dest node ref A2 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A2NodeRef))); - assertFalse("dest node ref A3 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertFalse("dest node ref A4 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A4NodeRef))); - assertFalse("dest node ref A5 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - - // Check injected transferred aspect. - assertNotNull("transferredAspect", (String)nodeService.getProperty(destNodeRef, TransferModel.PROP_REPOSITORY_ID)); - } - finally - { - endTransaction(); - } - + // Check injected transferred aspect. + assertNotNull("transferredAspect", (String) nodeService.getProperty(testData.destNodeRef, TransferModel.PROP_REPOSITORY_ID)); + return null; + } + }); /** - * Step 5. Add back A3. + * Step 5. Add back A3. */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_CONTENT); - A3NodeRef = child.getChildRef(); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_NAME, "A3"); - - ContentWriter writer = contentService.getWriter(A3NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - finally - { - endTransaction(); - } - startNewTransaction(); - try - { - /** - * Transfer Node A 1-5 - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - //nodes.add(A2NodeRef); - nodes.add(A3NodeRef); - //nodes.add(A4NodeRef); - //nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally - { - endTransaction(); - } + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_CONTENT); + testData.A3NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_NAME, "A3"); - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A1NodeRef))); - assertFalse("dest node ref A2 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A2NodeRef))); - assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertFalse("dest node ref A4 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A4NodeRef))); - assertFalse("dest node ref A5 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - - // Check injected transferred aspect. - assertNotNull("transferredAspect", (String)nodeService.getProperty(destNodeRef, TransferModel.PROP_REPOSITORY_ID)); - } - finally - { - endTransaction(); - } - - /** - * Step 6. add A2, A4, A5 - */ - startNewTransaction(); - try - { - - { - // Node A2 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_FOLDER); - A2NodeRef = child.getChildRef(); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_NAME, "A2"); - } - - { - // Node A4 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A4"), ContentModel.TYPE_CONTENT); - A4NodeRef = child.getChildRef(); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_NAME, "A4"); - - ContentWriter writer = contentService.getWriter(A4NodeRef, ContentModel.PROP_CONTENT, true); + ContentWriter writer = contentService.getWriter(testData.A3NodeRef, ContentModel.PROP_CONTENT, true); writer.setLocale(CONTENT_LOCALE); writer.putContent(CONTENT_STRING); + return null; } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable { - // Node A5 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_CONTENT); - A5NodeRef = child.getChildRef(); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_NAME, "A5"); - - ContentWriter writer = contentService.getWriter(A5NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); + /** + * Transfer Node A 1-5 + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + // nodes.add(A2NodeRef); + nodes.add(testData.A3NodeRef); + // nodes.add(A4NodeRef); + // nodes.add(A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; } - } - finally + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - startNewTransaction(); - try - { - /** - * Transfer Node A 1-5 - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - nodes.add(A2NodeRef); - nodes.add(A3NodeRef); - nodes.add(A4NodeRef); - nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally - { - endTransaction(); - } + // Now validate that the target node exists and has similar properties to the source + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A1NodeRef))); + assertFalse("dest node ref A2 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A2NodeRef))); + assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertFalse("dest node ref A4 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A4NodeRef))); + assertFalse("dest node ref A5 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); - startNewTransaction(); - try + // Check injected transferred aspect. + assertNotNull("transferredAspect", (String) nodeService.getProperty(testData.destNodeRef, TransferModel.PROP_REPOSITORY_ID)); + return null; + } + }); + /** + * Step 6. add A2, A4, A5 + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - // Now validate that the target node exists and has similar properties to the source - destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A1NodeRef))); - assertTrue("dest node A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A2NodeRef))); - assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertTrue("dest node A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A4NodeRef))); - assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - - // Check injected transferred aspect. - assertNotNull("transferredAspect", (String)nodeService.getProperty(destNodeRef, TransferModel.PROP_REPOSITORY_ID)); - } - finally + @Override + public Void execute() throws Throwable + { + + { + // Node A2 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_FOLDER); + testData.A2NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_NAME, "A2"); + } + + { + // Node A4 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A4"), ContentModel.TYPE_CONTENT); + testData.A4NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_NAME, "A4"); + + ContentWriter writer = contentService.getWriter(testData.A4NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + { + // Node A5 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_CONTENT); + testData.A5NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_NAME, "A5"); + + ContentWriter writer = contentService.getWriter(testData.A5NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + /** + * Transfer Node A 1-5 + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + nodes.add(testData.A2NodeRef); + nodes.add(testData.A3NodeRef); + nodes.add(testData.A4NodeRef); + nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A1NodeRef))); + assertTrue("dest node A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A2NodeRef))); + assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertTrue("dest node A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A4NodeRef))); + assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + + // Check injected transferred aspect. + assertNotNull("transferredAspect", (String) nodeService.getProperty(testData.destNodeRef, TransferModel.PROP_REPOSITORY_ID)); + return null; + } + }); /** * Step 7 - test delete and restore of a single node * remove A3 . @@ -1109,107 +1102,102 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest * restore node A3 * transfer */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - logger.debug("Step 7 - delete node A3"); - nodeService.deleteNode(A3NodeRef); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - /** - * Transfer Node A 1-5 - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - nodes.add(A2NodeRef); - //nodes.add(A3NodeRef); A3 has gone. - nodes.add(A4NodeRef); - nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally + logger.debug("Step 7 - delete node A3"); + nodeService.deleteNode(testData.A3NodeRef); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A1NodeRef))); - assertTrue("dest node ref A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A2NodeRef))); - assertFalse("dest node ref A3 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertTrue("dest node ref A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A4NodeRef))); - assertTrue("dest node ref A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - } - finally - { - endTransaction(); - } - startNewTransaction(); - try - { - NodeRef archivedNode = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, A3NodeRef.getId()); - NodeRef newNodeRef = nodeService.restoreNode(archivedNode, A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3")); - assertEquals("restored node ref is different", newNodeRef, A3NodeRef); - logger.debug("Step 7 - restore node A3"); - } - finally - { - endTransaction(); - } - startNewTransaction(); - try - { - /** - * Transfer Node A 1-5. - * (So we are seeing what happens to node 3 on the target - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - nodes.add(A2NodeRef); - nodes.add(A3NodeRef); - nodes.add(A4NodeRef); - nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally + /** + * Transfer Node A 1-5 + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + nodes.add(testData.A2NodeRef); + // nodes.add(testData.A3NodeRef); A3 has gone. + nodes.add(testData.A4NodeRef); + nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A1NodeRef))); + assertTrue("dest node ref A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A2NodeRef))); + assertFalse("dest node ref A3 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertTrue("dest node ref A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A4NodeRef))); + assertTrue("dest node ref A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - // Now validate that the target node exists and has similar properties to the source - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A1NodeRef))); - assertTrue("dest node ref A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A2NodeRef))); - assertTrue("dest node ref A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertTrue("dest node ref A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A4NodeRef))); - assertTrue("dest node ref A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - } - finally + @Override + public Void execute() throws Throwable + { + NodeRef archivedNode = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, testData.A3NodeRef.getId()); + NodeRef newNodeRef = nodeService.restoreNode(archivedNode, testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3")); + assertEquals("restored node ref is different", newNodeRef, testData.A3NodeRef); + logger.debug("Step 7 - restore node A3"); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + /** + * Transfer Node A 1-5. (So we are seeing what happens to node 3 on the target + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + nodes.add(testData.A2NodeRef); + nodes.add(testData.A3NodeRef); + nodes.add(testData.A4NodeRef); + nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A1NodeRef))); + assertTrue("dest node ref A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A2NodeRef))); + assertTrue("dest node ref A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertTrue("dest node ref A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A4NodeRef))); + assertTrue("dest node ref A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + return null; + } + }); /** * Step 8 - test delete and restore of a tree * remove A2 (A4, A5) should cascade delete. @@ -1217,110 +1205,105 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest * restore node A2 * transfer */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - nodeService.deleteNode(A2NodeRef); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - /** - * Transfer Node A 1-5 - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - //nodes.add(A2NodeRef); - nodes.add(A3NodeRef); - //nodes.add(A4NodeRef); - //nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A1NodeRef))); - assertFalse("dest node ref A2 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A2NodeRef))); - assertTrue("dest node ref A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertFalse("dest node ref A4 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A4NodeRef))); - assertFalse("dest node ref A5 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); + nodeService.deleteNode(testData.A2NodeRef); + return null; } - finally + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - startNewTransaction(); - try - { - NodeRef archivedNode = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, A2NodeRef.getId()); - nodeService.restoreNode(archivedNode, A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2")); - } - finally - { - endTransaction(); - } - startNewTransaction(); - try - { - /** - * Transfer Node A 1-5. - * (So we are seeing what happens to node 2, 4, 5 on the target - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - nodes.add(A2NodeRef); - nodes.add(A3NodeRef); - nodes.add(A4NodeRef); - nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally + /** + * Transfer Node A 1-5 + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + // nodes.add(testData.A2NodeRef); + nodes.add(testData.A3NodeRef); + // nodes.add(testData.A4NodeRef); + // nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A1NodeRef))); + assertFalse("dest node ref A2 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A2NodeRef))); + assertTrue("dest node ref A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertFalse("dest node ref A4 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A4NodeRef))); + assertFalse("dest node ref A5 has not been deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - // Now validate that the target node exists and has similar properties to the source - destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A1NodeRef))); - assertTrue("dest node ref A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A2NodeRef))); - assertTrue("dest node ref A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertTrue("dest node ref A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A4NodeRef))); - assertTrue("dest node ref A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - } - finally + @Override + public Void execute() throws Throwable + { + NodeRef archivedNode = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, testData.A2NodeRef.getId()); + nodeService.restoreNode(archivedNode, testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2")); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + /** + * Transfer Node A 1-5. (So we are seeing what happens to node 2, 4, 5 on the target + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + nodes.add(testData.A2NodeRef); + nodes.add(testData.A3NodeRef); + nodes.add(testData.A4NodeRef); + nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref A1 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A1NodeRef))); + assertTrue("dest node ref A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A2NodeRef))); + assertTrue("dest node ref A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertTrue("dest node ref A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A4NodeRef))); + assertTrue("dest node ref A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + return null; + } + }); } - + /** * Test the transfer method behaviour with respect to sync with (local) alien nodes. * @@ -1365,11 +1348,11 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest public void testTransferInvadedByLocalAlienNodes() throws Exception { setDefaultRollback(false); - - String CONTENT_TITLE = "ContentTitle"; - String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; - Locale CONTENT_LOCALE = Locale.JAPAN; - String CONTENT_STRING = "Hello"; + + final String CONTENT_TITLE = "ContentTitle"; + final String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + final Locale CONTENT_LOCALE = Locale.JAPAN; + final String CONTENT_STRING = "Hello"; /** * For unit test @@ -1380,697 +1363,657 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); - + final String localRepositoryId = descriptorService.getCurrentRepositoryDescriptor().getId(); - + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_A); transferServiceImpl.setDescriptorService(mockedDescriptorService); - - /** - * Now go ahead and create our first transfer target - */ - String targetName = "testSyncWithAlienNodes"; - TransferTarget transferMe; - - NodeRef A1NodeRef; - NodeRef A2NodeRef; - NodeRef A3NodeRef; - NodeRef A4NodeRef; - NodeRef A5NodeRef; - NodeRef A6NodeRef; - NodeRef A7NodeRef; - NodeRef A8NodeRef; - NodeRef B9NodeRef; - NodeRef B10NodeRef; - NodeRef B11NodeRef; - NodeRef B12NodeRef; - NodeRef B13NodeRef; - NodeRef B14NodeRef; - - NodeRef destNodeRef; - - startNewTransaction(); - try - { - /** - * Get guest home - */ - String guestHomeQuery = "/app:company_home/app:guest_home"; - ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); - assertEquals("", 1, guestHomeResult.length()); - NodeRef guestHome = guestHomeResult.getNodeRef(0); - - /** - * Create a test nodes A1 through A8 that we will read and write - */ - { - // Node A1 - String name = GUID.generate(); - ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); - A1NodeRef = child.getChildRef(); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_NAME, name); - } - - { - // Node A2 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_FOLDER); - A2NodeRef = child.getChildRef(); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_NAME, "A2"); - } - - { - // Node A3 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_CONTENT); - A3NodeRef = child.getChildRef(); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_NAME, "A3"); - - ContentWriter writer = contentService.getWriter(A3NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - { - // Node A4 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A4"), ContentModel.TYPE_FOLDER); - A4NodeRef = child.getChildRef(); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_NAME, "A4"); - } - { - // Node A5 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_CONTENT); - A5NodeRef = child.getChildRef(); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_NAME, "A5"); - - ContentWriter writer = contentService.getWriter(A5NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - - { - // Node A6 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A6"), ContentModel.TYPE_FOLDER); - A6NodeRef = child.getChildRef(); - nodeService.setProperty(A6NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A6NodeRef, ContentModel.PROP_NAME, "A6"); - } - { - // Node A7 - ChildAssociationRef child = nodeService.createNode(A4NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A7"), ContentModel.TYPE_CONTENT); - A7NodeRef = child.getChildRef(); - nodeService.setProperty(A7NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A7NodeRef, ContentModel.PROP_NAME, "A7"); - - ContentWriter writer = contentService.getWriter(A7NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - { - // Node A8 - ChildAssociationRef child = nodeService.createNode(A6NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A8"), ContentModel.TYPE_CONTENT); - A8NodeRef = child.getChildRef(); - nodeService.setProperty(A8NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A8NodeRef, ContentModel.PROP_NAME, "A8"); - - ContentWriter writer = contentService.getWriter(A8NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - - // Create the transfer target if it does not already exist - if(!transferService.targetExists(targetName)) - { - transferMe = createTransferTarget(targetName); - } else - { - transferMe = transferService.getTransferTarget(targetName); - } - } - finally - { - endTransaction(); - } - /** - * Step 1. add A1, A2, A3, A4, A5, A6, A7, A8 - * transfer(sync) + * Now go ahead and create our first transfer target */ - startNewTransaction(); - try + final String targetName = "testSyncWithAlienNodes"; + + class TestData { - /** - * Transfer Nodes A1 through A8 - */ + TransferTarget transferMe; + NodeRef A1NodeRef; + NodeRef A2NodeRef; + NodeRef A3NodeRef; + NodeRef A4NodeRef; + NodeRef A5NodeRef; + NodeRef A6NodeRef; + NodeRef A7NodeRef; + NodeRef A8NodeRef; + NodeRef B9NodeRef; + NodeRef B10NodeRef; + NodeRef B11NodeRef; + NodeRef B12NodeRef; + NodeRef B13NodeRef; + NodeRef B14NodeRef; + NodeRef destNodeRef; + } + final TestData testData = new TestData(); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - nodes.add(A2NodeRef); - nodes.add(A3NodeRef); - nodes.add(A4NodeRef); - nodes.add(A5NodeRef); - nodes.add(A6NodeRef); - nodes.add(A7NodeRef); - nodes.add(A8NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally - { - endTransaction(); - } + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - assertFalse("unit test stuffed up - comparing with self", A1destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef)); - assertEquals("title is wrong", (String)nodeService.getProperty(A1destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE); - assertEquals("type is wrong", nodeService.getType(A1NodeRef), nodeService.getType(A1destNodeRef)); - assertFalse("A1 is alien", nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - - // Check injected transferred aspect. - assertNotNull("transferredAspect", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); - } - finally - { - endTransaction(); - } - - /** - * Step 2 add Alien node B9 child of A1(dest). A1(dest) becomes Alien because it contains an alien child. - */ - startNewTransaction(); - try - { - destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - ChildAssociationRef child = nodeService.createNode(destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B9"), ContentModel.TYPE_CONTENT); - B9NodeRef = child.getChildRef(); - nodeService.setProperty(B9NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(B9NodeRef, ContentModel.PROP_NAME, "B9"); - - ContentWriter writer = contentService.getWriter(B9NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - - assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef)); - // Check injected transferred aspect. - assertTrue("node A1 is not alien aspect", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertNotNull("repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); - assertNotNull("from repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID)); - assertTrue("node B9 is not alien", (Boolean)nodeService.hasAspect(B9NodeRef, TransferModel.ASPECT_ALIEN)); - - // Temp code - List invaders = (List) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_INVADED_BY); - assertTrue("invaders contains local repository Id", invaders.contains(localRepositoryId)); - assertFalse("invaders contains REPO_ID_A", invaders.contains(REPO_ID_A)); + /** + * Create a test nodes A1 through A8 that we will read and write + */ + { + // Node A1 + String name = GUID.generate(); + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); + testData.A1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_NAME, name); + } - } - finally - { - endTransaction(); - } - - /** - * Step 3 remove alien node B9. A1 becomes non Alien. - */ - startNewTransaction(); - try - { - logger.debug("delete node B9"); - nodeService.deleteNode(B9NodeRef); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A3destNodeRef = testNodeFactory.getMappedNodeRef(A3NodeRef); - List invaders = (List) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_INVADED_BY); - assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef)); - assertFalse("node A1 is still alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertFalse("node A3 is alien", (Boolean)nodeService.hasAspect(A3destNodeRef, TransferModel.ASPECT_ALIEN)); - assertNotNull("repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); - assertNotNull("from repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID)); - } - finally - { - endTransaction(); - } - - /** - * 4 add Alien node B10 child of A2. A1 and A2 become Alien - */ - startNewTransaction(); - try - { - destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - ChildAssociationRef child = nodeService.createNode(destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B10"), ContentModel.TYPE_CONTENT); - B10NodeRef = child.getChildRef(); - nodeService.setProperty(B10NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(B10NodeRef, ContentModel.PROP_NAME, "B10"); - - ContentWriter writer = contentService.getWriter(B10NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - - assertTrue("node A1 is not alien aspect", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A2 is not alien aspect", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - } - - finally - { - endTransaction(); - } - - /** - * 5 remove Alien node B10. A1 and A2 become non Alien - */ - startNewTransaction(); - try - { - logger.debug("delete node B10"); - nodeService.deleteNode(B10NodeRef); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - - assertFalse("node A1 is still alien aspect", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertFalse("node A2 is still alien aspect", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - } - - finally - { - endTransaction(); - } - - /** - * Step 6 - * add B12 (child of A6) and B14 A6, A2, A1 becomes Alien - */ - startNewTransaction(); - try - { - destNodeRef = testNodeFactory.getMappedNodeRef(A6NodeRef); - ChildAssociationRef child = nodeService.createNode(destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B12"), ContentModel.TYPE_FOLDER); - B12NodeRef = child.getChildRef(); - nodeService.setProperty(B12NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(B12NodeRef, ContentModel.PROP_NAME, "B12"); - - child = nodeService.createNode(B12NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B14"), ContentModel.TYPE_CONTENT); - B14NodeRef = child.getChildRef(); - nodeService.setProperty(B14NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(B14NodeRef, ContentModel.PROP_NAME, "B14"); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(A6NodeRef); - - assertTrue("node A1 is not alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A2 is not alien", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A6 is not alien", (Boolean)nodeService.hasAspect(A6destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node B14 is not alien", (Boolean)nodeService.hasAspect(B14NodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node B12 is not alien", (Boolean)nodeService.hasAspect(B12NodeRef, TransferModel.ASPECT_ALIEN)); - } - - finally - { - endTransaction(); - } - - /** - * Step 7 - * Delete B14. B12 remains alien - */ - startNewTransaction(); - try - { - nodeService.deleteNode(B14NodeRef); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(A6NodeRef); - - assertTrue("node A1 is not alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A2 is not alien", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A6 is not alien", (Boolean)nodeService.hasAspect(A6destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node B12 is not alien", (Boolean)nodeService.hasAspect(B12NodeRef, TransferModel.ASPECT_ALIEN)); - } - - finally - { - endTransaction(); - } + { + // Node A2 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_FOLDER); + testData.A2NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_NAME, "A2"); + } + + { + // Node A3 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_CONTENT); + testData.A3NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_NAME, "A3"); + + ContentWriter writer = contentService.getWriter(testData.A3NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + { + // Node A4 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A4"), ContentModel.TYPE_FOLDER); + testData.A4NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_NAME, "A4"); + } + { + // Node A5 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_CONTENT); + testData.A5NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_NAME, "A5"); + + ContentWriter writer = contentService.getWriter(testData.A5NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + + { + // Node A6 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A6"), ContentModel.TYPE_FOLDER); + testData.A6NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A6NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A6NodeRef, ContentModel.PROP_NAME, "A6"); + } + { + // Node A7 + ChildAssociationRef child = nodeService.createNode(testData.A4NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A7"), ContentModel.TYPE_CONTENT); + testData.A7NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A7NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A7NodeRef, ContentModel.PROP_NAME, "A7"); + + ContentWriter writer = contentService.getWriter(testData.A7NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + { + // Node A8 + ChildAssociationRef child = nodeService.createNode(testData.A6NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A8"), ContentModel.TYPE_CONTENT); + testData.A8NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A8NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A8NodeRef, ContentModel.PROP_NAME, "A8"); + + ContentWriter writer = contentService.getWriter(testData.A8NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + + // Create the transfer target if it does not already exist + if (!transferService.targetExists(targetName)) + { + testData.transferMe = createTransferTarget(targetName); + } + else + { + testData.transferMe = transferService.getTransferTarget(targetName); + } + return null; + } + }); /** - * Step 8 - * add B13 A6, A2, A1 remains Alien + * Step 1. add A1, A2, A3, A4, A5, A6, A7, A8 transfer(sync) */ - startNewTransaction(); - try - { - destNodeRef = testNodeFactory.getMappedNodeRef(A6NodeRef); - ChildAssociationRef child = nodeService.createNode(destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B13"), ContentModel.TYPE_CONTENT); - B13NodeRef = child.getChildRef(); - nodeService.setProperty(B13NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(B13NodeRef, ContentModel.PROP_NAME, "B13"); - - ContentWriter writer = contentService.getWriter(B13NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(A6NodeRef); - - assertTrue("node A1 is not alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A2 is not alien", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A6 is not alien", (Boolean)nodeService.hasAspect(A6destNodeRef, TransferModel.ASPECT_ALIEN)); - } - - finally - { - endTransaction(); - } - - /** - * Step 9 remove B13 A6, A2, A1 remains Alien Due to B12 - */ - startNewTransaction(); - try - { - nodeService.deleteNode(B13NodeRef); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(A6NodeRef); - - assertTrue("node A1 is not alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A2 is not alien", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A6 is not alien", (Boolean)nodeService.hasAspect(A6destNodeRef, TransferModel.ASPECT_ALIEN)); - } - - finally - { - endTransaction(); - } + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + /** + * Transfer Nodes A1 through A8 + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + nodes.add(testData.A2NodeRef); + nodes.add(testData.A3NodeRef); + nodes.add(testData.A4NodeRef); + nodes.add(testData.A5NodeRef); + nodes.add(testData.A6NodeRef); + nodes.add(testData.A7NodeRef); + nodes.add(testData.A8NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + assertFalse("unit test stuffed up - comparing with self", A1destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef)); + assertEquals("title is wrong", (String) nodeService.getProperty(A1destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE); + assertEquals("type is wrong", nodeService.getType(testData.A1NodeRef), nodeService.getType(A1destNodeRef)); + assertFalse("A1 is alien", nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - /** - * Step 10 remove B12 A6, A2, A1 becomes non Alien. - */ - startNewTransaction(); - try - { - nodeService.deleteNode(B12NodeRef); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(A6NodeRef); - - assertFalse("node A1 is still alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertFalse("node A2 is still alien", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - assertFalse("node A6 is still alien", (Boolean)nodeService.hasAspect(A6destNodeRef, TransferModel.ASPECT_ALIEN)); - } - - finally - { - endTransaction(); - } + // Check injected transferred aspect. + assertNotNull("transferredAspect", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); + return null; + } + }); + /** + * Step 2 add Alien node B9 child of A1(dest). A1(dest) becomes Alien because it contains an alien child. + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + ChildAssociationRef child = nodeService.createNode(testData.destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B9"), ContentModel.TYPE_CONTENT); + testData.B9NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B9NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.B9NodeRef, ContentModel.PROP_NAME, "B9"); - /** - * Step 11 add B9 and B10 A1 and A2 become Alien - */ - startNewTransaction(); - try - { - destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - ChildAssociationRef child = nodeService.createNode(destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B9"), ContentModel.TYPE_CONTENT); - B9NodeRef = child.getChildRef(); - nodeService.setProperty(B9NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(B9NodeRef, ContentModel.PROP_NAME, "B9"); - - ContentWriter writer = contentService.getWriter(B9NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - - destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - child = nodeService.createNode(destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B10"), ContentModel.TYPE_CONTENT); - B10NodeRef = child.getChildRef(); - nodeService.setProperty(B10NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(B10NodeRef, ContentModel.PROP_NAME, "B10"); - - writer = contentService.getWriter(B10NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - - assertTrue("node A1 is not alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A2 is not alien", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - } - finally - { - endTransaction(); - } + ContentWriter writer = contentService.getWriter(testData.B9NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); - - /** - * Step 12 remove B10 A2 becomes non alien A1 remains alien. - */ - startNewTransaction(); - try - { - nodeService.deleteNode(B10NodeRef); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - - // BUGBUG - assertTrue("node A1 is still alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertFalse("node A2 is still alien", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - } - - finally - { - endTransaction(); - } + assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef)); + // Check injected transferred aspect. + assertTrue("node A1 is not alien aspect", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertNotNull("repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); + assertNotNull("from repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID)); + assertTrue("node B9 is not alien", (Boolean) nodeService.hasAspect(testData.B9NodeRef, TransferModel.ASPECT_ALIEN)); + + // Temp code + List invaders = (List) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_INVADED_BY); + assertTrue("invaders contains local repository Id", invaders.contains(localRepositoryId)); + assertFalse("invaders contains REPO_ID_A", invaders.contains(REPO_ID_A)); + return null; + } + }); + /** + * Step 3 remove alien node B9. A1 becomes non Alien. + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + logger.debug("delete node B9"); + nodeService.deleteNode(testData.B9NodeRef); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A3destNodeRef = testNodeFactory.getMappedNodeRef(testData.A3NodeRef); + List invaders = (List) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_INVADED_BY); + assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef)); + assertFalse("node A1 is still alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertFalse("node A3 is alien", (Boolean) nodeService.hasAspect(A3destNodeRef, TransferModel.ASPECT_ALIEN)); + assertNotNull("repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); + assertNotNull("from repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID)); + return null; + } + }); + /** + * 4 add Alien node B10 child of A2. A1 and A2 become Alien + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + ChildAssociationRef child = nodeService.createNode(testData.destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B10"), ContentModel.TYPE_CONTENT); + testData.B10NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B10NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.B10NodeRef, ContentModel.PROP_NAME, "B10"); + + ContentWriter writer = contentService.getWriter(testData.B10NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + + assertTrue("node A1 is not alien aspect", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A2 is not alien aspect", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); + /** + * 5 remove Alien node B10. A1 and A2 become non Alien + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + logger.debug("delete node B10"); + nodeService.deleteNode(testData.B10NodeRef); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + + assertFalse("node A1 is still alien aspect", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertFalse("node A2 is still alien aspect", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); + /** + * Step 6 add B12 (child of A6) and B14 A6, A2, A1 becomes Alien + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A6NodeRef); + ChildAssociationRef child = nodeService.createNode(testData.destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B12"), ContentModel.TYPE_FOLDER); + testData.B12NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B12NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.B12NodeRef, ContentModel.PROP_NAME, "B12"); + + child = nodeService.createNode(testData.B12NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B14"), ContentModel.TYPE_CONTENT); + testData.B14NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B14NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.B14NodeRef, ContentModel.PROP_NAME, "B14"); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(testData.A6NodeRef); + + assertTrue("node A1 is not alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A2 is not alien", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A6 is not alien", (Boolean) nodeService.hasAspect(A6destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node B14 is not alien", (Boolean) nodeService.hasAspect(testData.B14NodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node B12 is not alien", (Boolean) nodeService.hasAspect(testData.B12NodeRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); + /** + * Step 7 Delete B14. B12 remains alien + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + nodeService.deleteNode(testData.B14NodeRef); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(testData.A6NodeRef); + + assertTrue("node A1 is not alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A2 is not alien", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A6 is not alien", (Boolean) nodeService.hasAspect(A6destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node B12 is not alien", (Boolean) nodeService.hasAspect(testData.B12NodeRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); + /** + * Step 8 add B13 A6, A2, A1 remains Alien + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A6NodeRef); + ChildAssociationRef child = nodeService.createNode(testData.destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B13"), ContentModel.TYPE_CONTENT); + testData.B13NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B13NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.B13NodeRef, ContentModel.PROP_NAME, "B13"); + + ContentWriter writer = contentService.getWriter(testData.B13NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(testData.A6NodeRef); + + assertTrue("node A1 is not alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A2 is not alien", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A6 is not alien", (Boolean) nodeService.hasAspect(A6destNodeRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); + /** + * Step 9 remove B13 A6, A2, A1 remains Alien Due to B12 + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + nodeService.deleteNode(testData.B13NodeRef); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(testData.A6NodeRef); + + assertTrue("node A1 is not alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A2 is not alien", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A6 is not alien", (Boolean) nodeService.hasAspect(A6destNodeRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); + /** + * Step 10 remove B12 A6, A2, A1 becomes non Alien. + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + nodeService.deleteNode(testData.B12NodeRef); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(testData.A6NodeRef); + + assertFalse("node A1 is still alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertFalse("node A2 is still alien", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + assertFalse("node A6 is still alien", (Boolean) nodeService.hasAspect(A6destNodeRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); + /** + * Step 11 add B9 and B10 A1 and A2 become Alien + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + ChildAssociationRef child = nodeService.createNode(testData.destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B9"), ContentModel.TYPE_CONTENT); + testData.B9NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B9NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.B9NodeRef, ContentModel.PROP_NAME, "B9"); + + ContentWriter writer = contentService.getWriter(testData.B9NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + child = nodeService.createNode(testData.destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B10"), ContentModel.TYPE_CONTENT); + testData.B10NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B10NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.B10NodeRef, ContentModel.PROP_NAME, "B10"); + + writer = contentService.getWriter(testData.B10NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + + assertTrue("node A1 is not alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A2 is not alien", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); + + /** + * Step 12 remove B10 A2 becomes non alien A1 remains alien. + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + nodeService.deleteNode(testData.B10NodeRef); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + + // BUGBUG + assertTrue("node A1 is still alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertFalse("node A2 is still alien", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); + + /** + * 13 Add Alien node B11. + */ + logger.debug("Step 12 Add Node B11, Delete A2 and sync"); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A4NodeRef); + ChildAssociationRef child = nodeService.createNode(testData.destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B11"), ContentModel.TYPE_CONTENT); + testData.B11NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B11NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.B11NodeRef, ContentModel.PROP_NAME, "B11"); + + ContentWriter writer = contentService.getWriter(testData.B11NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + + nodeService.deleteNode(testData.A2NodeRef); + return null; + } + }); + /** + * Step 14 delete A2 (will cascade delete A4, A5, A6, A7, A8 transfer sync (A5, A6, A7, A8 and should be deleted + * A2 and A4 remain since they contain alien content.) + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate A1, A2 and A4 are alien + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + NodeRef A4destNodeRef = testNodeFactory.getMappedNodeRef(testData.A4NodeRef); + + assertTrue("node A1 is not alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A2 is not alien", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A4 is not alien", (Boolean) nodeService.hasAspect(A4destNodeRef, TransferModel.ASPECT_ALIEN)); + + assertFalse("test error: node A2 not deleted", nodeService.exists(testData.A2NodeRef)); + assertFalse("test error: node A4 not deleted", nodeService.exists(testData.A4NodeRef)); + assertFalse("test error: node A5 not deleted", nodeService.exists(testData.A5NodeRef)); + assertFalse("test error: node A6 not deleted", nodeService.exists(testData.A6NodeRef)); + assertFalse("test error: node A7 not deleted", nodeService.exists(testData.A7NodeRef)); + assertFalse("test error: node A8 not deleted", nodeService.exists(testData.A8NodeRef)); + + assertTrue("test error: node does not exist", nodeService.exists(testData.A3NodeRef)); + + /** + * Transfer Nodes A1 through A8 + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + nodes.add(testData.A3NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists and has similar properties to the source + NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + NodeRef A3destNodeRef = testNodeFactory.getMappedNodeRef(testData.A3NodeRef); + NodeRef A4destNodeRef = testNodeFactory.getMappedNodeRef(testData.A4NodeRef); + NodeRef A5destNodeRef = testNodeFactory.getMappedNodeRef(testData.A5NodeRef); + NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(testData.A6NodeRef); + NodeRef A7destNodeRef = testNodeFactory.getMappedNodeRef(testData.A7NodeRef); + NodeRef A8destNodeRef = testNodeFactory.getMappedNodeRef(testData.A8NodeRef); + + assertTrue("node A1 not alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A2 not alien", (Boolean) nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node A4 not alien", (Boolean) nodeService.hasAspect(A4destNodeRef, TransferModel.ASPECT_ALIEN)); + assertTrue("node B11 does not exist", nodeService.exists(testData.B11NodeRef)); + + assertTrue("node A3 deleted", nodeService.exists(A3destNodeRef)); + + assertFalse("node A5 not deleted", nodeService.exists(A5destNodeRef)); + assertFalse("node A6 not deleted", nodeService.exists(A6destNodeRef)); + assertFalse("node A7 not deleted", nodeService.exists(A7destNodeRef)); + assertFalse("node A8 not deleted", nodeService.exists(A8destNodeRef)); + return null; + } + }); + } - - /** - * 13 Add Alien node B11. - */ - logger.debug("Step 12 Add Node B11, Delete A2 and sync"); - startNewTransaction(); - try - { - destNodeRef = testNodeFactory.getMappedNodeRef(A4NodeRef); - ChildAssociationRef child = nodeService.createNode(destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B11"), ContentModel.TYPE_CONTENT); - B11NodeRef = child.getChildRef(); - nodeService.setProperty(B11NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(B11NodeRef, ContentModel.PROP_NAME, "B11"); - - ContentWriter writer = contentService.getWriter(B11NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - - nodeService.deleteNode(A2NodeRef); - } - finally - { - endTransaction(); - } - - /** - * Step 14 - * delete A2 (will cascade delete A4, A5, A6, A7, A8 - * transfer sync - * (A5, A6, A7, A8 and should be deleted A2 and A4 remain since they contain alien content.) - */ - startNewTransaction(); - try - { - // Now validate A1, A2 and A4 are alien - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - NodeRef A4destNodeRef = testNodeFactory.getMappedNodeRef(A4NodeRef); - - - assertTrue("node A1 is not alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A2 is not alien", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A4 is not alien", (Boolean)nodeService.hasAspect(A4destNodeRef, TransferModel.ASPECT_ALIEN)); - - assertFalse("test error: node A2 not deleted", nodeService.exists(A2NodeRef)); - assertFalse("test error: node A4 not deleted", nodeService.exists(A4NodeRef)); - assertFalse("test error: node A5 not deleted", nodeService.exists(A5NodeRef)); - assertFalse("test error: node A6 not deleted", nodeService.exists(A6NodeRef)); - assertFalse("test error: node A7 not deleted", nodeService.exists(A7NodeRef)); - assertFalse("test error: node A8 not deleted", nodeService.exists(A8NodeRef)); - - assertTrue("test error: node does not exist", nodeService.exists(A3NodeRef)); - - /** - * Transfer Nodes A1 through A8 - */ - { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - nodes.add(A3NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists and has similar properties to the source - NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef); - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - NodeRef A3destNodeRef = testNodeFactory.getMappedNodeRef(A3NodeRef); - NodeRef A4destNodeRef = testNodeFactory.getMappedNodeRef(A4NodeRef); - NodeRef A5destNodeRef = testNodeFactory.getMappedNodeRef(A5NodeRef); - NodeRef A6destNodeRef = testNodeFactory.getMappedNodeRef(A6NodeRef); - NodeRef A7destNodeRef = testNodeFactory.getMappedNodeRef(A7NodeRef); - NodeRef A8destNodeRef = testNodeFactory.getMappedNodeRef(A8NodeRef); - - assertTrue("node A1 not alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A2 not alien", (Boolean)nodeService.hasAspect(A2destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node A4 not alien", (Boolean)nodeService.hasAspect(A4destNodeRef, TransferModel.ASPECT_ALIEN)); - assertTrue("node B11 does not exist", nodeService.exists(B11NodeRef)); - - assertTrue("node A3 deleted", nodeService.exists(A3destNodeRef)); - - assertFalse("node A5 not deleted", nodeService.exists(A5destNodeRef)); - assertFalse("node A6 not deleted", nodeService.exists(A6destNodeRef)); - assertFalse("node A7 not deleted", nodeService.exists(A7destNodeRef)); - assertFalse("node A8 not deleted", nodeService.exists(A8destNodeRef)); - } - - finally - { - endTransaction(); - } - } - /** * Test restore of a local node. *
    @@ -2094,87 +2037,88 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest
         public void testLocalAlienRestore() throws Exception
         {
             setDefaultRollback(false);
    -        
    -        String CONTENT_TITLE = "ContentTitle";
    -        String CONTENT_TITLE_UPDATED = "ContentTitleUpdated";
    -        Locale CONTENT_LOCALE = Locale.JAPAN; 
    -        String CONTENT_STRING = "Hello";
     
    -          
    +        final String CONTENT_TITLE = "ContentTitle";
    +        final String CONTENT_TITLE_UPDATED = "ContentTitleUpdated";
    +        final Locale CONTENT_LOCALE = Locale.JAPAN;
    +        final String CONTENT_STRING = "Hello";
    +
             /**
    -          * Now go ahead and create our first transfer target
    -          */
    -        String targetName = "testRestoreOfAlienNodes";
    -        TransferTarget transferMe;
    -        
    -        NodeRef S0NodeRef;
    -        NodeRef A0NodeRef;
    -        NodeRef A1NodeRef;
    -        NodeRef B1NodeRef;
    -        NodeRef B2NodeRef;
    -        NodeRef B3NodeRef;
    -        
    -        NodeRef destNodeRef;
    -        
    -        startNewTransaction();
    -        try
    +         * Now go ahead and create our first transfer target
    +         */
    +        final String targetName = "testRestoreOfAlienNodes";
    +
    +        class TestData
             {
    -            /**
    -              * Get guest home
    -              */
    -            String guestHomeQuery = "/app:company_home/app:guest_home";
    -            ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery);
    -            assertEquals("", 1, guestHomeResult.length());
    -            NodeRef guestHome = guestHomeResult.getNodeRef(0); 
    -    
    -            /**
    -             * Create a test nodes A1 through A8 that we will read and write
    -             */
    -            {
    -                // Node S0
    -                String name = GUID.generate();
    -                ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER);
    -                S0NodeRef = child.getChildRef();
    -                nodeService.setProperty(S0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);   
    -                nodeService.setProperty(S0NodeRef, ContentModel.PROP_NAME, name);
    -            }
    -            {
    -                // Node A1
    -                ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A0"), ContentModel.TYPE_FOLDER);
    -                A0NodeRef = child.getChildRef();
    -                nodeService.setProperty(A0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);   
    -                nodeService.setProperty(A0NodeRef, ContentModel.PROP_NAME, "A0");
    -            }
    -            {
    -                // Node A1
    -                ChildAssociationRef child = nodeService.createNode(A0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER);
    -                A1NodeRef = child.getChildRef();
    -                nodeService.setProperty(A1NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);   
    -                nodeService.setProperty(A1NodeRef, ContentModel.PROP_NAME, "A1");
    -            }
    -            {
    -                // Node B1
    -                ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B1"), ContentModel.TYPE_FOLDER);
    -                B1NodeRef = child.getChildRef();
    -                nodeService.setProperty(B1NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);   
    -                nodeService.setProperty(B1NodeRef, ContentModel.PROP_NAME, "B1");
    -            }
    - 
    -            // Create the transfer target if it does not already exist
    -            if(!transferService.targetExists(targetName))
    -            {
    -                transferMe = createTransferTarget(targetName);
    -            }            
    -            else
    -            {
    -                transferMe = transferService.getTransferTarget(targetName);
    -            }
    +            TransferTarget transferMe;
    +            NodeRef S0NodeRef;
    +            NodeRef A0NodeRef;
    +            NodeRef A1NodeRef;
    +            NodeRef B1NodeRef;
    +            NodeRef B2NodeRef;
    +            NodeRef B3NodeRef;
    +            NodeRef destNodeRef;
             }
    -        finally
    +        final TestData testData = new TestData();
    +
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            endTransaction();
    -        }
    -        
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                /**
    +                 * Get guest home
    +                 */
    +                String guestHomeQuery = "/app:company_home/app:guest_home";
    +                ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery);
    +                assertEquals("", 1, guestHomeResult.length());
    +                NodeRef guestHome = guestHomeResult.getNodeRef(0);
    +
    +                /**
    +                 * Create a test nodes A1 through A8 that we will read and write
    +                 */
    +                {
    +                    // Node S0
    +                    String name = GUID.generate();
    +                    ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER);
    +                    testData.S0NodeRef = child.getChildRef();
    +                    nodeService.setProperty(testData.S0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);
    +                    nodeService.setProperty(testData.S0NodeRef, ContentModel.PROP_NAME, name);
    +                }
    +                {
    +                    // Node A1
    +                    ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A0"), ContentModel.TYPE_FOLDER);
    +                    testData.A0NodeRef = child.getChildRef();
    +                    nodeService.setProperty(testData.A0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);
    +                    nodeService.setProperty(testData.A0NodeRef, ContentModel.PROP_NAME, "A0");
    +                }
    +                {
    +                    // Node A1
    +                    ChildAssociationRef child = nodeService.createNode(testData.A0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER);
    +                    testData.A1NodeRef = child.getChildRef();
    +                    nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);
    +                    nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_NAME, "A1");
    +                }
    +                {
    +                    // Node B1
    +                    ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B1"), ContentModel.TYPE_FOLDER);
    +                    testData.B1NodeRef = child.getChildRef();
    +                    nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);
    +                    nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_NAME, "B1");
    +                }
    +
    +                // Create the transfer target if it does not already exist
    +                if (!transferService.targetExists(targetName))
    +                {
    +                    testData.transferMe = createTransferTarget(targetName);
    +                }
    +                else
    +                {
    +                    testData.transferMe = transferService.getTransferTarget(targetName);
    +                }
    +                return null;
    +            }
    +        });
             /**
              *  For unit test 
              *  - replace the HTTP transport with the in-process transport
    @@ -2184,229 +2128,214 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest
              */
             TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService);
             transferServiceImpl.setTransmitter(transmitter);
    -        UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); 
    -        transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); 
    +        final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory);
    +        transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory);
             List> pathMap = testNodeFactory.getPathMap();
    -        pathMap.add(new Pair(nodeService.getPath(A0NodeRef), nodeService.getPath(B1NodeRef)));     
    +        pathMap.add(new Pair(nodeService.getPath(testData.A0NodeRef), nodeService.getPath(testData.B1NodeRef)));
             DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_A);
             transferServiceImpl.setDescriptorService(mockedDescriptorService);
    -        
    -        /**
    -         * Step 1. add A1
    -         *   transfer(sync)
    -         */
    -        startNewTransaction();
    -        try 
    -        {
    -           /**
    -             * Transfer Nodes A1
    -             */
    -            {
    -                TransferDefinition definition = new TransferDefinition();
    -                Setnodes = new HashSet();
    -                nodes.add(A1NodeRef);
    -                definition.setNodes(nodes);
    -                definition.setSync(true);
    -                transferService.transfer(targetName, definition);
    -            }  
    -        }
    -        finally
    -        {
    -            endTransaction();
    -        }
     
    -        startNewTransaction();
    -        try
    +        /**
    +         * Step 1. add A1 transfer(sync)
    +         */
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            // Now validate that the target node exists and has similar properties to the source
    -            NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef);
    -            assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef));
    -            assertFalse("A1 is alien", nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN));       
    -            // Check injected transferred aspect.
    -            assertNotNull("transferredAspect", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); 
    -        }
    -        finally
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                /**
    +                 * Transfer Nodes A1
    +                 */
    +                {
    +                    TransferDefinition definition = new TransferDefinition();
    +                    Set nodes = new HashSet();
    +                    nodes.add(testData.A1NodeRef);
    +                    definition.setNodes(nodes);
    +                    definition.setSync(true);
    +                    transferService.transfer(targetName, definition);
    +                }
    +                return null;
    +            }
    +        });
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            endTransaction();
    -        }
    -        
    -      /**
    -        * Step 2 add Alien node B1 child of A1(dest).  
    -        */
    -        startNewTransaction();
    -        try 
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                // Now validate that the target node exists and has similar properties to the source
    +                NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef);
    +                assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef));
    +                assertFalse("A1 is alien", nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN));
    +                // Check injected transferred aspect.
    +                assertNotNull("transferredAspect", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID));
    +                return null;
    +            }
    +        });
    +        /**
    +         * Step 2 add Alien node B1 child of A1(dest).
    +         */
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef);
    -            ChildAssociationRef child = nodeService.createNode(destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B2"), ContentModel.TYPE_FOLDER);
    -            B2NodeRef = child.getChildRef();
    -            nodeService.setProperty(B2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);   
    -            nodeService.setProperty(B2NodeRef, ContentModel.PROP_NAME, "B2");
    -        }
    -        finally
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef);
    +                ChildAssociationRef child = nodeService.createNode(testData.destNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B2"), ContentModel.TYPE_FOLDER);
    +                testData.B2NodeRef = child.getChildRef();
    +                nodeService.setProperty(testData.B2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);
    +                nodeService.setProperty(testData.B2NodeRef, ContentModel.PROP_NAME, "B2");
    +                return null;
    +            }
    +        });
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            endTransaction();
    -        }
    -        
    -        startNewTransaction();
    -        try
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                // Now validate that the target node exists and has similar properties to the source
    +                NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef);
    +
    +                assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef));
    +                // Check injected transferred aspect.
    +                assertTrue("node A1 is not alien aspect", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN));
    +                assertNotNull("repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID));
    +                assertNotNull("from repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID));
    +                assertTrue("node B2 is not alien", (Boolean) nodeService.hasAspect(testData.B2NodeRef, TransferModel.ASPECT_ALIEN));
    +                return null;
    +            }
    +        });
    +        /**
    +         * Step 3 remove alien node B2. A1 becomes non Alien.
    +         */
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            // Now validate that the target node exists and has similar properties to the source
    -            NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef);
    -            
    -            assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef));
    -            // Check injected transferred aspect.        
    -            assertTrue("node A1 is not alien aspect", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN));     
    -            assertNotNull("repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); 
    -            assertNotNull("from repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID)); 
    -            assertTrue("node B2 is not alien", (Boolean)nodeService.hasAspect(B2NodeRef, TransferModel.ASPECT_ALIEN));
    -        }
    -        finally
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                logger.debug("delete node B2");
    +                nodeService.deleteNode(testData.B2NodeRef);
    +                return null;
    +            }
    +        });
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            endTransaction();
    -        }
    -       
    -       /** 
    -        * Step 3 remove alien node B2.  A1 becomes non Alien.
    -        */ 
    -        startNewTransaction();
    -        try 
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef);
    +                List invaders = (List) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_INVADED_BY);
    +                assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef));
    +                assertFalse("node A1 is still alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN));
    +                assertNotNull("repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID));
    +                assertNotNull("from repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID));
    +                return null;
    +            }
    +        });
    +        /**
    +         * Step 4 restore alien node B2. A1 becomes Alien again
    +         */
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            logger.debug("delete node B2");
    -            nodeService.deleteNode(B2NodeRef);
    -        }
    -        finally
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                logger.debug("restore node B2");
    +                NodeRef B2ArchiveNodeRef = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, testData.B2NodeRef.getId());
    +                nodeService.restoreNode(B2ArchiveNodeRef, testNodeFactory.getMappedNodeRef(testData.A1NodeRef), ContentModel.ASSOC_CONTAINS, QName.createQName("B2"));
    +                return null;
    +            }
    +        });
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            endTransaction();
    -        }
    -        
    -        startNewTransaction();
    -        try
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef);
    +                assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef));
    +                assertTrue("node A1 is not alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN));
    +                assertNotNull("repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID));
    +                assertNotNull("from repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID));
    +                return null;
    +            }
    +        });
    +        /**
    +         * Step 5 - add B3
    +         */
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef);
    -            List invaders =  (List) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_INVADED_BY);
    -            assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef));
    -            assertFalse("node A1 is still alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN));  
    -            assertNotNull("repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); 
    -            assertNotNull("from repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID)); 
    -        }
    -        finally
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                ChildAssociationRef child = nodeService.createNode(testData.B2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B3"), ContentModel.TYPE_FOLDER);
    +                testData.B3NodeRef = child.getChildRef();
    +                nodeService.setProperty(testData.B3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);
    +                nodeService.setProperty(testData.B3NodeRef, ContentModel.PROP_NAME, "B3");
    +                return null;
    +            }
    +        });
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
             {
    -            endTransaction();
    -        }
    -        
    -        /** 
    -         * Step 4 restore alien node B2.  A1 becomes Alien again
    -         */ 
    -         startNewTransaction();
    -         try 
    -         {
    -             logger.debug("restore node B2");
    -             NodeRef B2ArchiveNodeRef = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, B2NodeRef.getId());
    -             nodeService.restoreNode(B2ArchiveNodeRef, testNodeFactory.getMappedNodeRef(A1NodeRef),  ContentModel.ASSOC_CONTAINS, QName.createQName("B2"));
    -         }
    -         finally
    -         {
    -             endTransaction();
    -         }
    -         
    -         startNewTransaction();
    -         try
    -         {
    -             NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef);
    -             assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef));
    -             assertTrue("node A1 is not alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN));  
    -             assertNotNull("repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); 
    -             assertNotNull("from repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID));      
    -         }
    -         finally
    -         {
    -             endTransaction();
    -         }
    -         
    -         /**
    -          * Step 5 - add B3
    -          */
    -          startNewTransaction();
    -          try 
    -          {
    -              ChildAssociationRef child = nodeService.createNode(B2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B3"), ContentModel.TYPE_FOLDER);
    -              B3NodeRef = child.getChildRef();
    -              nodeService.setProperty(B3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE);   
    -              nodeService.setProperty(B3NodeRef, ContentModel.PROP_NAME, "B3");
    -          }
    -          finally
    -          {
    -              endTransaction();
    -          }
    -          startNewTransaction();
    -          try
    -          {
    -              assertTrue("node B3 is not alien", (Boolean)nodeService.hasAspect(B3NodeRef, TransferModel.ASPECT_ALIEN));     
    -          }
    -          finally
    -          {
    -              endTransaction();
    -          }
    -          
    -          /** 
    -           * Step 5 remove alien node B2.  A1 becomes non Alien (again).
    -           */ 
    -           startNewTransaction();
    -           try 
    -           {
    -               logger.debug("delete node B2");
    -               nodeService.deleteNode(B2NodeRef);
    -           }
    -           finally
    -           {
    -               endTransaction();
    -           }
    -           
    -           startNewTransaction();
    -           try
    -           {
    -               NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(A1NodeRef);
    -               List invaders =  (List) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_INVADED_BY);
    -               assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef));
    -               assertFalse("node A1 is still alien", (Boolean)nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN));  
    -               assertNotNull("repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID)); 
    -               assertNotNull("from repository id is null", (String)nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID)); 
    -           }
    -           finally
    -           {
    -               endTransaction();
    -           }
    -           
    -           /**
    -            * Step6 restore B2 and B3 to B1. 
    -            */
    -           startNewTransaction();
    -           try 
    -           {
    -               logger.debug("restore node B2");
    -               NodeRef B2ArchiveNodeRef = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, B2NodeRef.getId());
    -               nodeService.restoreNode(B2ArchiveNodeRef, B1NodeRef,  ContentModel.ASSOC_CONTAINS, QName.createQName("B2"));
    -           }
    -           finally
    -           {
    -               endTransaction();
    -           }
    -           
    -           startNewTransaction();
    -           try
    -           {
    -               assertFalse("node A1 is still alien", (Boolean)nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A1NodeRef), TransferModel.ASPECT_ALIEN));  
    -               assertFalse("node A2 is still alien", (Boolean)nodeService.hasAspect(B2NodeRef, TransferModel.ASPECT_ALIEN));  
    -               assertFalse("node A3 is still alien", (Boolean)nodeService.hasAspect(B3NodeRef, TransferModel.ASPECT_ALIEN));  
    -           }
    -           finally
    -           {
    -               endTransaction();
    -           }
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                assertTrue("node B3 is not alien", (Boolean) nodeService.hasAspect(testData.B3NodeRef, TransferModel.ASPECT_ALIEN));
    +                return null;
    +            }
    +        });
    +        /**
    +         * Step 5 remove alien node B2. A1 becomes non Alien (again).
    +         */
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
    +        {
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                logger.debug("delete node B2");
    +                nodeService.deleteNode(testData.B2NodeRef);
    +                return null;
    +            }
    +        });
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
    +        {
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                NodeRef A1destNodeRef = testNodeFactory.getMappedNodeRef(testData.A1NodeRef);
    +                List invaders = (List) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_INVADED_BY);
    +                assertTrue("dest node ref does not exist", nodeService.exists(A1destNodeRef));
    +                assertFalse("node A1 is still alien", (Boolean) nodeService.hasAspect(A1destNodeRef, TransferModel.ASPECT_ALIEN));
    +                assertNotNull("repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_REPOSITORY_ID));
    +                assertNotNull("from repository id is null", (String) nodeService.getProperty(A1destNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID));
    +                return null;
    +            }
    +        });
    +        /**
    +         * Step6 restore B2 and B3 to B1.
    +         */
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
    +        {
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                logger.debug("restore node B2");
    +                NodeRef B2ArchiveNodeRef = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, testData.B2NodeRef.getId());
    +                nodeService.restoreNode(B2ArchiveNodeRef, testData.B1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B2"));
    +                return null;
    +            }
    +        });
    +        transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
    +        {
    +            @Override
    +            public Void execute() throws Throwable
    +            {
    +                assertFalse("node A1 is still alien", (Boolean) nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A1NodeRef), TransferModel.ASPECT_ALIEN));
    +                assertFalse("node A2 is still alien", (Boolean) nodeService.hasAspect(testData.B2NodeRef, TransferModel.ASPECT_ALIEN));
    +                assertFalse("node A3 is still alien", (Boolean) nodeService.hasAspect(testData.B3NodeRef, TransferModel.ASPECT_ALIEN));
    +                return null;
    +            }
    +        });
         }
    -    
    -    
    -       
    +
         /**
          * Test the transfer method with regard to permissions on a node.
          * 

    @@ -2436,132 +2365,132 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest public void testTransferWithPermissions() throws Exception { setDefaultRollback(false); - - String CONTENT_TITLE = "ContentTitle"; - String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; - Locale CONTENT_LOCALE = Locale.GERMAN; - String CONTENT_STRING = "Hello"; + + final String CONTENT_TITLE = "ContentTitle"; + final String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + final Locale CONTENT_LOCALE = Locale.GERMAN; + final String CONTENT_STRING = "Hello"; /** - * For unit test - * - replace the HTTP transport with the in-process transport - * - replace the node factory with one that will map node refs, paths etc. + * For unit test - replace the HTTP transport with the in-process transport - replace the node factory with one + * that will map node refs, paths etc. */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); - + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_A); transferServiceImpl.setDescriptorService(mockedDescriptorService); - + /** - * Now go ahead and create our transfer target - */ - String targetName = "testTransferWithPermissions"; - TransferTarget transferMe; - NodeRef contentNodeRef; - NodeRef destNodeRef; - - startNewTransaction(); - try + * Now go ahead and create our transfer target + */ + final String targetName = "testTransferWithPermissions"; + class TestData { - /** - * Get guest home - */ - String guestHomeQuery = "/app:company_home/app:guest_home"; - ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); - assertEquals("", 1, guestHomeResult.length()); - NodeRef guestHome = guestHomeResult.getNodeRef(0); - - /** - * Create a test node that we will read and write - */ - String name = GUID.generate(); - ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_CONTENT); - contentNodeRef = child.getChildRef(); - nodeService.setProperty(contentNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(contentNodeRef, ContentModel.PROP_NAME, name); - - ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - - permissionService.setInheritParentPermissions(contentNodeRef, false); - permissionService.setPermission(contentNodeRef, "admin", PermissionService.READ, true); - - if(!transferService.targetExists(targetName)) - { - transferMe = createTransferTarget(targetName); - } - else - { - transferMe = transferService.getTransferTarget(targetName); - } + TransferTarget transferMe; + NodeRef contentNodeRef; + NodeRef destNodeRef; } - finally + final TestData testData = new TestData(); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + /** + * Create a test node that we will read and write + */ + String name = GUID.generate(); + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_CONTENT); + testData.contentNodeRef = child.getChildRef(); + nodeService.setProperty(testData.contentNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.contentNodeRef, ContentModel.PROP_NAME, name); + + ContentWriter writer = contentService.getWriter(testData.contentNodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + + permissionService.setInheritParentPermissions(testData.contentNodeRef, false); + permissionService.setPermission(testData.contentNodeRef, "admin", PermissionService.READ, true); + + if (!transferService.targetExists(targetName)) + { + testData.transferMe = createTransferTarget(targetName); + } + else + { + testData.transferMe = transferService.getTransferTarget(targetName); + } + return null; + } + }); /** * Step 1 */ logger.debug("First transfer - create new node with inheritParent permission off"); - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - /** - * Transfer our transfer target node - */ + @Override + public Void execute() throws Throwable { + /** + * Transfer our transfer target node + */ + { TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(contentNodeRef); + Set nodes = new HashSet(); + nodes.add(testData.contentNodeRef); definition.setNodes(nodes); transferService.transfer(targetName, definition); - } - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists with the correct permissions - destNodeRef = testNodeFactory.getMappedNodeRef(contentNodeRef); - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref does not exist", nodeService.exists(destNodeRef)); - assertEquals("title is wrong", (String)nodeService.getProperty(destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE); - assertEquals("type is wrong", nodeService.getType(contentNodeRef), nodeService.getType(destNodeRef)); - - // Check ACL of destination node - boolean srcInherit = permissionService.getInheritParentPermissions(contentNodeRef); - Set srcPerm = permissionService.getAllSetPermissions(contentNodeRef); - - boolean destInherit = permissionService.getInheritParentPermissions(destNodeRef); - Set destPerm = permissionService.getAllSetPermissions(destNodeRef); - - assertFalse("inherit parent permissions (src) flag is incorrect", srcInherit); - assertFalse("inherit parent permissions (dest) flag is incorrect", destInherit); - - // Check destination has the source's permissions - for (AccessPermission p : srcPerm) - { - logger.debug("checking permission :" + p); - assertTrue("permission is missing", destPerm.contains(p)); + } + return null; } - } - finally + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists with the correct permissions + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.contentNodeRef); + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref does not exist", nodeService.exists(testData.destNodeRef)); + assertEquals("title is wrong", (String) nodeService.getProperty(testData.destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE); + assertEquals("type is wrong", nodeService.getType(testData.contentNodeRef), nodeService.getType(testData.destNodeRef)); + + // Check ACL of destination node + boolean srcInherit = permissionService.getInheritParentPermissions(testData.contentNodeRef); + Set srcPerm = permissionService.getAllSetPermissions(testData.contentNodeRef); + + boolean destInherit = permissionService.getInheritParentPermissions(testData.destNodeRef); + Set destPerm = permissionService.getAllSetPermissions(testData.destNodeRef); + + assertFalse("inherit parent permissions (src) flag is incorrect", srcInherit); + assertFalse("inherit parent permissions (dest) flag is incorrect", destInherit); + + // Check destination has the source's permissions + for (AccessPermission p : srcPerm) + { + logger.debug("checking permission :" + p); + assertTrue("permission is missing", destPerm.contains(p)); + } + return null; + } + }); /** * Step 2 * Update it to have several permissions @@ -2569,195 +2498,185 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest * Read, Everyone, DENY * Read, Admin, Allow */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - permissionService.setPermission(contentNodeRef, "EVERYONE", PermissionService.READ, false); - permissionService.setPermission(contentNodeRef, "admin", PermissionService.FULL_CONTROL, true); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - /** - * Transfer our transfer target node - */ + @Override + public Void execute() throws Throwable { + permissionService.setPermission(testData.contentNodeRef, "EVERYONE", PermissionService.READ, false); + permissionService.setPermission(testData.contentNodeRef, "admin", PermissionService.FULL_CONTROL, true); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + /** + * Transfer our transfer target node + */ + { TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(contentNodeRef); + Set nodes = new HashSet(); + nodes.add(testData.contentNodeRef); definition.setNodes(nodes); transferService.transfer(targetName, definition); - } - } - finally - { - endTransaction(); - } - - - startNewTransaction(); - try - { - // Now validate that the target node exists with the correct permissions - destNodeRef = testNodeFactory.getMappedNodeRef(contentNodeRef); - - // Check ACL of destination node - boolean srcInherit = permissionService.getInheritParentPermissions(contentNodeRef); - Set srcPerm = permissionService.getAllSetPermissions(contentNodeRef); - - boolean destInherit = permissionService.getInheritParentPermissions(destNodeRef); - Set destPerm = permissionService.getAllSetPermissions(destNodeRef); - - assertFalse("inherit parent permissions (src) flag is incorrect", srcInherit); - assertFalse("inherit parent permissions (dest) flag is incorrect", destInherit); - - // Check destination has the source's permissions - for (AccessPermission p : srcPerm) - { - logger.debug("checking permission :" + p); - assertTrue("Step2, permission is missing", destPerm.contains(p)); + } + return null; } - } - finally + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists with the correct permissions + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.contentNodeRef); + + // Check ACL of destination node + boolean srcInherit = permissionService.getInheritParentPermissions(testData.contentNodeRef); + Set srcPerm = permissionService.getAllSetPermissions(testData.contentNodeRef); + + boolean destInherit = permissionService.getInheritParentPermissions(testData.destNodeRef); + Set destPerm = permissionService.getAllSetPermissions(testData.destNodeRef); + + assertFalse("inherit parent permissions (src) flag is incorrect", srcInherit); + assertFalse("inherit parent permissions (dest) flag is incorrect", destInherit); + + // Check destination has the source's permissions + for (AccessPermission p : srcPerm) + { + logger.debug("checking permission :" + p); + assertTrue("Step2, permission is missing", destPerm.contains(p)); + } + return null; + } + }); /** * Step 3 Remove a permission */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - permissionService.deletePermission(contentNodeRef, "admin", PermissionService.FULL_CONTROL); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - /** - * Transfer our transfer target node - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(contentNodeRef); - definition.setNodes(nodes); - transferService.transfer(targetName, definition); - } - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists with the correct permissions - destNodeRef = testNodeFactory.getMappedNodeRef(contentNodeRef); - - // Check ACL of destination node - boolean srcInherit = permissionService.getInheritParentPermissions(contentNodeRef); - Set srcPerm = permissionService.getAllSetPermissions(contentNodeRef); - - boolean destInherit = permissionService.getInheritParentPermissions(destNodeRef); - Set destPerm = permissionService.getAllSetPermissions(destNodeRef); - - assertFalse("inherit parent permissions (src) flag is incorrect", srcInherit); - assertFalse("inherit parent permissions (dest) flag is incorrect", destInherit); - - // Check destination has the source's permissions - for (AccessPermission p : srcPerm) - { - logger.debug("checking permission :" + p); - assertTrue("permission is missing", destPerm.contains(p)); + permissionService.deletePermission(testData.contentNodeRef, "admin", PermissionService.FULL_CONTROL); + return null; } - } - finally + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - /** - * Step 4 - * Revert to inherit all permissions - */ - startNewTransaction(); - try - { - permissionService.setInheritParentPermissions(contentNodeRef, true); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - /** - * Transfer our transfer target node - */ + @Override + public Void execute() throws Throwable { + /** + * Transfer our transfer target node + */ + { TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(contentNodeRef); + Set nodes = new HashSet(); + nodes.add(testData.contentNodeRef); definition.setNodes(nodes); transferService.transfer(targetName, definition); - } - } - finally + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists with the correct permissions - destNodeRef = testNodeFactory.getMappedNodeRef(contentNodeRef); - assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); - assertTrue("dest node ref does not exist", nodeService.exists(destNodeRef)); - assertEquals("title is wrong", (String)nodeService.getProperty(destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE); - assertEquals("type is wrong", nodeService.getType(contentNodeRef), nodeService.getType(destNodeRef)); - - // Check ACL of destination node - boolean srcInherit = permissionService.getInheritParentPermissions(contentNodeRef); - Set srcPerm = permissionService.getAllSetPermissions(contentNodeRef); - - boolean destInherit = permissionService.getInheritParentPermissions(destNodeRef); - Set destPerm = permissionService.getAllSetPermissions(destNodeRef); - - assertTrue("inherit parent permissions (src) flag is incorrect", srcInherit); - assertTrue("inherit parent permissions (dest) flag is incorrect", destInherit); - - // Check destination has the source's permissions - for (AccessPermission p : srcPerm) + @Override + public Void execute() throws Throwable { - if(p.isSetDirectly()) + // Now validate that the target node exists with the correct permissions + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.contentNodeRef); + + // Check ACL of destination node + boolean srcInherit = permissionService.getInheritParentPermissions(testData.contentNodeRef); + Set srcPerm = permissionService.getAllSetPermissions(testData.contentNodeRef); + + boolean destInherit = permissionService.getInheritParentPermissions(testData.destNodeRef); + Set destPerm = permissionService.getAllSetPermissions(testData.destNodeRef); + + assertFalse("inherit parent permissions (src) flag is incorrect", srcInherit); + assertFalse("inherit parent permissions (dest) flag is incorrect", destInherit); + + // Check destination has the source's permissions + for (AccessPermission p : srcPerm) { logger.debug("checking permission :" + p); - assertTrue("permission is missing:" + p, destPerm.contains(p)); + assertTrue("permission is missing", destPerm.contains(p)); } + return null; } - } - finally + }); + /** + * Step 4 Revert to inherit all permissions + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - } - - + @Override + public Void execute() throws Throwable + { + permissionService.setInheritParentPermissions(testData.contentNodeRef, true); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + /** + * Transfer our transfer target node + */ + { + TransferDefinition definition = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(testData.contentNodeRef); + definition.setNodes(nodes); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // Now validate that the target node exists with the correct permissions + testData.destNodeRef = testNodeFactory.getMappedNodeRef(testData.contentNodeRef); + assertFalse("unit test stuffed up - comparing with self", testData.destNodeRef.equals(testData.transferMe.getNodeRef())); + assertTrue("dest node ref does not exist", nodeService.exists(testData.destNodeRef)); + assertEquals("title is wrong", (String) nodeService.getProperty(testData.destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE); + assertEquals("type is wrong", nodeService.getType(testData.contentNodeRef), nodeService.getType(testData.destNodeRef)); + + // Check ACL of destination node + boolean srcInherit = permissionService.getInheritParentPermissions(testData.contentNodeRef); + Set srcPerm = permissionService.getAllSetPermissions(testData.contentNodeRef); + + boolean destInherit = permissionService.getInheritParentPermissions(testData.destNodeRef); + Set destPerm = permissionService.getAllSetPermissions(testData.destNodeRef); + + assertTrue("inherit parent permissions (src) flag is incorrect", srcInherit); + assertTrue("inherit parent permissions (dest) flag is incorrect", destInherit); + + // Check destination has the source's permissions + for (AccessPermission p : srcPerm) + { + if (p.isSetDirectly()) + { + logger.debug("checking permission :" + p); + assertTrue("permission is missing:" + p, destPerm.contains(p)); + } + } + return null; + } + }); + } + /** * Transfer with read only flag * @@ -2789,25 +2708,31 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest public void testReadOnlyFlag() throws Exception { setDefaultRollback(false); - - String CONTENT_TITLE = "ContentTitle"; - String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; - String CONTENT_NAME = "Demo Node 1"; - Locale CONTENT_LOCALE = Locale.GERMAN; - String CONTENT_STRING = "The quick brown fox"; - Setnodes = new HashSet(); - String USER_ONE = "TransferServiceImplTest"; - String PASSWORD = "Password"; - - String targetName = "testReadOnlyFlag"; - - NodeRef nodeA; - NodeRef nodeB; - NodeRef nodeC; - NodeRef nodeD; - - ChildAssociationRef child; - + + final String CONTENT_TITLE = "ContentTitle"; + final String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + final String CONTENT_NAME = "Demo Node 1"; + final Locale CONTENT_LOCALE = Locale.GERMAN; + final String CONTENT_STRING = "The quick brown fox"; + final Set nodes = new HashSet(); + final String USER_ONE = "TransferServiceImplTest"; + final String PASSWORD = "Password"; + + final String targetName = "testReadOnlyFlag"; + + class TestData + { + NodeRef nodeA; + NodeRef nodeB; + NodeRef nodeC; + NodeRef nodeD; + + ChildAssociationRef child; + + TransferTarget transferMe; + } + final TestData testData = new TestData(); + /** * For unit test * - replace the HTTP transport with the in-process transport @@ -2815,354 +2740,342 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(this.receiver, this.contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); - + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_A); transferServiceImpl.setDescriptorService(mockedDescriptorService); - - TransferTarget transferMe; - - startNewTransaction(); - try + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - /** - * Get guest home - */ - String guestHomeQuery = "/app:company_home/app:guest_home"; - ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); - assertEquals("", 1, guestHomeResult.length()); - NodeRef guestHome = guestHomeResult.getNodeRef(0); - - /** - * Create a test node that we will read and write - */ - String guid = GUID.generate(); - - child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(guid), ContentModel.TYPE_FOLDER); - nodeA = child.getChildRef(); - nodeService.setProperty(nodeA , ContentModel.PROP_TITLE, guid); - nodeService.setProperty(nodeA , ContentModel.PROP_NAME, guid); - nodes.add(nodeA); - - child = nodeService.createNode(nodeA, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeB"), ContentModel.TYPE_CONTENT); - nodeB = child.getChildRef(); - nodeService.setProperty(nodeB , ContentModel.PROP_TITLE, CONTENT_TITLE + "B"); - nodeService.setProperty(nodeB , ContentModel.PROP_NAME, "DemoNodeB"); - - { - ContentWriter writer = contentService.getWriter(nodeB , ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - nodes.add(nodeB); - } - - child = nodeService.createNode(nodeA, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,"testNodeC"), ContentModel.TYPE_FOLDER); - nodeC = child.getChildRef(); - nodeService.setProperty(nodeC , ContentModel.PROP_TITLE, "TestNodeC"); - nodeService.setProperty(nodeC , ContentModel.PROP_NAME, "TestNodeC"); - nodes.add(nodeC); - - child = nodeService.createNode(nodeC, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeD"), ContentModel.TYPE_CONTENT); - nodeD = child.getChildRef(); - nodeService.setProperty(nodeD , ContentModel.PROP_TITLE, CONTENT_TITLE + "D"); - nodeService.setProperty(nodeD , ContentModel.PROP_NAME, "DemoNodeD"); - { - ContentWriter writer = contentService.getWriter(nodeD , ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - nodes.add(nodeD); - } - - // Create users - createUser(USER_ONE, PASSWORD); - - /** - * Now go ahead and create our first transfer target - */ - if(!transferService.targetExists(targetName)) - { - transferMe = createTransferTarget(targetName); - } - else - { - transferMe = transferService.getTransferTarget(targetName); - } - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + /** + * Create a test node that we will read and write + */ + String guid = GUID.generate(); + + testData.child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(guid), ContentModel.TYPE_FOLDER); + testData.nodeA = testData.child.getChildRef(); + nodeService.setProperty(testData.nodeA, ContentModel.PROP_TITLE, guid); + nodeService.setProperty(testData.nodeA, ContentModel.PROP_NAME, guid); + nodes.add(testData.nodeA); + + testData.child = nodeService.createNode(testData.nodeA, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeB"), ContentModel.TYPE_CONTENT); + testData.nodeB = testData.child.getChildRef(); + nodeService.setProperty(testData.nodeB, ContentModel.PROP_TITLE, CONTENT_TITLE + "B"); + nodeService.setProperty(testData.nodeB, ContentModel.PROP_NAME, "DemoNodeB"); + + { + ContentWriter writer = contentService.getWriter(testData.nodeB, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + nodes.add(testData.nodeB); + } + + testData.child = nodeService.createNode(testData.nodeA, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testNodeC"), ContentModel.TYPE_FOLDER); + testData.nodeC = testData.child.getChildRef(); + nodeService.setProperty(testData.nodeC, ContentModel.PROP_TITLE, "TestNodeC"); + nodeService.setProperty(testData.nodeC, ContentModel.PROP_NAME, "TestNodeC"); + nodes.add(testData.nodeC); + + testData.child = nodeService.createNode(testData.nodeC, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeD"), ContentModel.TYPE_CONTENT); + testData.nodeD = testData.child.getChildRef(); + nodeService.setProperty(testData.nodeD, ContentModel.PROP_TITLE, CONTENT_TITLE + "D"); + nodeService.setProperty(testData.nodeD, ContentModel.PROP_NAME, "DemoNodeD"); + { + ContentWriter writer = contentService.getWriter(testData.nodeD, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + nodes.add(testData.nodeD); + } + + // Create users + createUser(USER_ONE, PASSWORD); + + /** + * Now go ahead and create our first transfer target + */ + if (!transferService.targetExists(targetName)) + { + testData.transferMe = createTransferTarget(targetName); + } + else + { + testData.transferMe = transferService.getTransferTarget(targetName); + } + return null; + } + }); /** * Step 1. * transfer Nodes ABCD with read only flag set - content should all be locked on destination */ logger.debug("transfer read only - step 1"); - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - /** - * Transfer our transfer target nodes - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - definition.setNodes(nodes); - definition.setReadOnly(true); - transferService.transfer(targetName, definition); - } - } - finally + /** + * Transfer our transfer target nodes + */ + { + TransferDefinition definition = new TransferDefinition(); + definition.setNodes(nodes); + definition.setReadOnly(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try - { - // Check destination nodes are locked. - assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeA))); - assertTrue("dest node B does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeB))); - assertTrue("dest node C does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeC))); - assertTrue("dest node D does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeD))); - - assertTrue("dest node A not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeA), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node B not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeB), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node C not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeC), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node D not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeD), ContentModel.ASPECT_LOCKABLE)); - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + // Check destination nodes are locked. + assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeA))); + assertTrue("dest node B does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeB))); + assertTrue("dest node C does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeC))); + assertTrue("dest node D does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeD))); + + assertTrue("dest node A not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeA), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node B not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeB), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node C not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeC), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node D not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeD), ContentModel.ASPECT_LOCKABLE)); + return null; + } + }); /** * Step 2 * lock B (Content node) as user ONE * transfer (read only) - destination lock should change user to "Admin" */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - AuthenticationUtil.pushAuthentication(); - AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); - lockService.lock(nodeB, LockType.READ_ONLY_LOCK); - } - finally - { - assertEquals("test error: dest node B lock ownership", nodeService.getProperty(nodeB, ContentModel.PROP_LOCK_OWNER), USER_ONE); - AuthenticationUtil.popAuthentication(); - endTransaction(); - } - - logger.debug("transfer read only - step 2"); - startNewTransaction(); - try - { - /** - * Transfer our transfer target nodes - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - definition.setNodes(nodes); - definition.setReadOnly(true); - transferService.transfer(targetName, definition); - } - } - finally + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); + lockService.lock(testData.nodeB, LockType.READ_ONLY_LOCK); + assertEquals("test error: dest node B lock ownership", nodeService.getProperty(testData.nodeB, ContentModel.PROP_LOCK_OWNER), USER_ONE); + AuthenticationUtil.popAuthentication(); + return null; + } + }); + logger.debug("transfer read only - step 2"); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + /** + * Transfer our transfer target nodes + */ + { + TransferDefinition definition = new TransferDefinition(); + definition.setNodes(nodes); + definition.setReadOnly(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - // Check destination nodes are locked. - assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeA))); - assertTrue("dest node B does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeB))); - assertTrue("dest node C does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeC))); - assertTrue("dest node D does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeD))); - - assertTrue("dest node A not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeA), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node B not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeB), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node C not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeC), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node D not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeD), ContentModel.ASPECT_LOCKABLE)); - - // check that the lock owner is no longer USER_ONE - assertTrue("lock owner not changed", !USER_ONE.equalsIgnoreCase((String)nodeService.getProperty(testNodeFactory.getMappedNodeRef(nodeB), ContentModel.PROP_LOCK_OWNER))); - } - finally - { - endTransaction(); - } + @Override + public Void execute() throws Throwable + { + // Check destination nodes are locked. + assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeA))); + assertTrue("dest node B does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeB))); + assertTrue("dest node C does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeC))); + assertTrue("dest node D does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeD))); + + assertTrue("dest node A not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeA), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node B not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeB), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node C not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeC), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node D not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeD), ContentModel.ASPECT_LOCKABLE)); + + // check that the lock owner is no longer USER_ONE + assertTrue("lock owner not changed", !USER_ONE.equalsIgnoreCase((String) nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.nodeB), ContentModel.PROP_LOCK_OWNER))); + return null; + } + }); - /** * Step 3 * lock C (Folder node) as user ONE * transfer (read only) - destination lock should change to Admin */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - AuthenticationUtil.pushAuthentication(); - AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); - lockService.lock(nodeC, LockType.READ_ONLY_LOCK); - } - finally - { - assertEquals("test error: dest node C lock ownership", nodeService.getProperty(nodeC, ContentModel.PROP_LOCK_OWNER), USER_ONE); - AuthenticationUtil.popAuthentication(); - endTransaction(); - } - - logger.debug("transfer read only - step 3"); - startNewTransaction(); - try - { - /** - * Transfer our transfer target nodes - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - definition.setNodes(nodes); - definition.setReadOnly(true); - transferService.transfer(targetName, definition); - } - } - finally + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(USER_ONE); + lockService.lock(testData.nodeC, LockType.READ_ONLY_LOCK); + assertEquals("test error: dest node C lock ownership", nodeService.getProperty(testData.nodeC, ContentModel.PROP_LOCK_OWNER), USER_ONE); + AuthenticationUtil.popAuthentication(); + return null; + } + }); + logger.debug("transfer read only - step 3"); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + /** + * Transfer our transfer target nodes + */ + { + TransferDefinition definition = new TransferDefinition(); + definition.setNodes(nodes); + definition.setReadOnly(true); + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - // Check destination nodes are locked. - assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeA))); - assertTrue("dest node B does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeB))); - assertTrue("dest node C does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeC))); - assertTrue("dest node D does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeD))); - - assertTrue("dest node A not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeA), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node B not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeB), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node C not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeC), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node D not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeD), ContentModel.ASPECT_LOCKABLE)); - - // check that the lock owner is no longer USER_ONE for content node B and folder node C - assertTrue("lock owner not changed", !USER_ONE.equalsIgnoreCase((String)nodeService.getProperty(testNodeFactory.getMappedNodeRef(nodeB), ContentModel.PROP_LOCK_OWNER))); - assertTrue("lock owner not changed", !USER_ONE.equalsIgnoreCase((String)nodeService.getProperty(testNodeFactory.getMappedNodeRef(nodeC), ContentModel.PROP_LOCK_OWNER))); - } - finally - { - endTransaction(); - } + @Override + public Void execute() throws Throwable + { + // Check destination nodes are locked. + assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeA))); + assertTrue("dest node B does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeB))); + assertTrue("dest node C does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeC))); + assertTrue("dest node D does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeD))); + + assertTrue("dest node A not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeA), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node B not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeB), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node C not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeC), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node D not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeD), ContentModel.ASPECT_LOCKABLE)); + + // check that the lock owner is no longer USER_ONE for content node B and folder node C + assertTrue("lock owner not changed", !USER_ONE.equalsIgnoreCase((String) nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.nodeB), ContentModel.PROP_LOCK_OWNER))); + assertTrue("lock owner not changed", !USER_ONE.equalsIgnoreCase((String) nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.nodeC), ContentModel.PROP_LOCK_OWNER))); + return null; + } + }); - /** * Step 4 * transfer without read only flag - locks should revert from Admin to USER_ONE. */ logger.debug("transfer read only - step 4"); - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - /** - * Transfer our transfer target nodes - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - definition.setNodes(nodes); - definition.setReadOnly(false); // turn off read-only - transferService.transfer(targetName, definition); - } - } - finally + /** + * Transfer our transfer target nodes + */ + { + TransferDefinition definition = new TransferDefinition(); + definition.setNodes(nodes); + definition.setReadOnly(false); // turn off read-only + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - startNewTransaction(); - try - { - // Check destination nodes are not locked. - assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeA))); - assertTrue("dest node B does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeB))); - assertTrue("dest node C does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeC))); - assertTrue("dest node D does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeD))); - - assertFalse("dest node A not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeA), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node B not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeB), ContentModel.ASPECT_LOCKABLE)); - assertTrue("dest node C not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeC), ContentModel.ASPECT_LOCKABLE)); - assertFalse("dest node D not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeD), ContentModel.ASPECT_LOCKABLE)); - - assertEquals("dest node B lock ownership", nodeService.getProperty(testNodeFactory.getMappedNodeRef(nodeB), ContentModel.PROP_LOCK_OWNER), USER_ONE); - assertEquals("dest node C lock ownership", nodeService.getProperty(testNodeFactory.getMappedNodeRef(nodeC), ContentModel.PROP_LOCK_OWNER), USER_ONE); - - } - finally - { - endTransaction(); - } + @Override + public Void execute() throws Throwable + { + // Check destination nodes are not locked. + assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeA))); + assertTrue("dest node B does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeB))); + assertTrue("dest node C does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeC))); + assertTrue("dest node D does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeD))); + + assertFalse("dest node A not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeA), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node B not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeB), ContentModel.ASPECT_LOCKABLE)); + assertTrue("dest node C not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeC), ContentModel.ASPECT_LOCKABLE)); + assertFalse("dest node D not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeD), ContentModel.ASPECT_LOCKABLE)); + + assertEquals("dest node B lock ownership", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.nodeB), ContentModel.PROP_LOCK_OWNER), USER_ONE); + assertEquals("dest node C lock ownership", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.nodeC), ContentModel.PROP_LOCK_OWNER), USER_ONE); + + return null; + } + }); - /** * Step 5 * remove locks on A and B - transfer without read only flag - content should all be unlocked. */ logger.debug("transfer read only - step 5"); - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - lockService.unlock(nodeB); - lockService.unlock(nodeC); - } - finally - { - endTransaction(); - } - startNewTransaction(); - try - { - /** - * Transfer our transfer target nodes - */ + @Override + public Void execute() throws Throwable { - TransferDefinition definition = new TransferDefinition(); - definition.setNodes(nodes); - definition.setReadOnly(false); // turn off read-only - transferService.transfer(targetName, definition); - } - } - finally + lockService.unlock(testData.nodeB); + lockService.unlock(testData.nodeC); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + /** + * Transfer our transfer target nodes + */ + { + TransferDefinition definition = new TransferDefinition(); + definition.setNodes(nodes); + definition.setReadOnly(false); // turn off read-only + transferService.transfer(targetName, definition); + } + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - // Check destination nodes are not locked. - assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeA))); - assertTrue("dest node B does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeB))); - assertTrue("dest node C does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeC))); - assertTrue("dest node D does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(nodeD))); - - assertFalse("test fail: dest node B is still locked", nodeService.hasAspect(nodeB, ContentModel.ASPECT_LOCKABLE)); - assertFalse("test fail: dest node C is still locked", nodeService.hasAspect(nodeC, ContentModel.ASPECT_LOCKABLE)); - - assertFalse("dest node A not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeA), ContentModel.ASPECT_LOCKABLE)); - assertFalse("dest node B not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeB), ContentModel.ASPECT_LOCKABLE)); - assertFalse("dest node C not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeC), ContentModel.ASPECT_LOCKABLE)); - assertFalse("dest node D not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(nodeD), ContentModel.ASPECT_LOCKABLE)); - } - finally - { - endTransaction(); - } + @Override + public Void execute() throws Throwable + { + // Check destination nodes are not locked. + assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeA))); + assertTrue("dest node B does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeB))); + assertTrue("dest node C does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeC))); + assertTrue("dest node D does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.nodeD))); + + assertFalse("test fail: dest node B is still locked", nodeService.hasAspect(testData.nodeB, ContentModel.ASPECT_LOCKABLE)); + assertFalse("test fail: dest node C is still locked", nodeService.hasAspect(testData.nodeC, ContentModel.ASPECT_LOCKABLE)); + + assertFalse("dest node A not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeA), ContentModel.ASPECT_LOCKABLE)); + assertFalse("dest node B not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeB), ContentModel.ASPECT_LOCKABLE)); + assertFalse("dest node C not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeC), ContentModel.ASPECT_LOCKABLE)); + assertFalse("dest node D not locked", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.nodeD), ContentModel.ASPECT_LOCKABLE)); + return null; + } + }); } // end test read only flag - + /** * Transfer sync from multiple repos. * @@ -3206,12 +3119,12 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest * transfer (sync) */ setDefaultRollback(false); - - String CONTENT_TITLE = "ContentTitle"; - String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; - Locale CONTENT_LOCALE = Locale.GERMAN; - String CONTENT_STRING = "Hello"; - + + final String CONTENT_TITLE = "ContentTitle"; + final String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + final Locale CONTENT_LOCALE = Locale.GERMAN; + final String CONTENT_STRING = "Hello"; + /** * For unit test * - replace the HTTP transport with the in-process transport @@ -3219,116 +3132,119 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); - + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_A); transferServiceImpl.setDescriptorService(mockedDescriptorService); - String repositoryId = REPO_ID_A; - + final String repositoryId = REPO_ID_A; + /** - * Now go ahead and create our first transfer target - */ - String targetName = "testTransferSyncNodes"; - TransferTarget transferMe; - NodeRef A1NodeRef; - NodeRef A2NodeRef; - NodeRef A3NodeRef; - NodeRef A4NodeRef; - NodeRef A5NodeRef; - NodeRef B6NodeRef; - NodeRef A7NodeRef; - - startNewTransaction(); - try + * Now go ahead and create our first transfer target + */ + final String targetName = "testTransferSyncNodes"; + class TestData { - /** - * Get guest home - */ - String guestHomeQuery = "/app:company_home/app:guest_home"; - ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); - assertEquals("", 1, guestHomeResult.length()); - NodeRef guestHome = guestHomeResult.getNodeRef(0); - - /** - * Create a test nodes A1 through A5 that we will read and write - */ - { - // Node A1 - String name = GUID.generate(); - ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); - A1NodeRef = child.getChildRef(); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_NAME, name); - } - - { - // Node A2 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_FOLDER); - A2NodeRef = child.getChildRef(); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_NAME, "A2"); - } - - { - // Node A3 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_CONTENT); - A3NodeRef = child.getChildRef(); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_NAME, "A3"); - - ContentWriter writer = contentService.getWriter(A3NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - { - // Node A4 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A4"), ContentModel.TYPE_CONTENT); - A4NodeRef = child.getChildRef(); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_NAME, "A4"); - - ContentWriter writer = contentService.getWriter(A4NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - { - // Node A5 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_CONTENT); - A5NodeRef = child.getChildRef(); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_NAME, "A5"); - - ContentWriter writer = contentService.getWriter(A5NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - - // Create the transfer target if it does not already exist - if(!transferService.targetExists(targetName)) - { - transferMe = createTransferTarget(targetName); - } - else - { - transferMe = transferService.getTransferTarget(targetName); - } + TransferTarget transferMe; + NodeRef A1NodeRef; + NodeRef A2NodeRef; + NodeRef A3NodeRef; + NodeRef A4NodeRef; + NodeRef A5NodeRef; + NodeRef B6NodeRef; + NodeRef A7NodeRef; } - finally + final TestData testData = new TestData(); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - Setnodes = new HashSet(); - nodes.add(A1NodeRef); - nodes.add(A2NodeRef); - nodes.add(A3NodeRef); - nodes.add(A4NodeRef); - nodes.add(A5NodeRef); + @Override + public Void execute() throws Throwable + { + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + /** + * Create a test nodes A1 through A5 that we will read and write + */ + { + // Node A1 + String name = GUID.generate(); + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); + testData.A1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_NAME, name); + } + + { + // Node A2 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_FOLDER); + testData.A2NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_NAME, "A2"); + } + + { + // Node A3 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_CONTENT); + testData.A3NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_NAME, "A3"); + + ContentWriter writer = contentService.getWriter(testData.A3NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + { + // Node A4 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A4"), ContentModel.TYPE_CONTENT); + testData.A4NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_NAME, "A4"); + + ContentWriter writer = contentService.getWriter(testData.A4NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + { + // Node A5 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_CONTENT); + testData.A5NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_NAME, "A5"); + + ContentWriter writer = contentService.getWriter(testData.A5NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + + // Create the transfer target if it does not already exist + if (!transferService.targetExists(targetName)) + { + testData.transferMe = createTransferTarget(targetName); + } + else + { + testData.transferMe = transferService.getTransferTarget(targetName); + } + return null; + } + }); + final Set nodes = new HashSet(); + nodes.add(testData.A1NodeRef); + nodes.add(testData.A2NodeRef); + nodes.add(testData.A3NodeRef); + nodes.add(testData.A4NodeRef); + nodes.add(testData.A5NodeRef); /** * transfer (sync) @@ -3336,164 +3252,158 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest * create node B6. Fake its transfered aspect to be from Repo B, Non Alien. * transfer (sync) */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { + @Override + public Void execute() throws Throwable { TransferDefinition definition = new TransferDefinition(); definition.setNodes(nodes); definition.setSync(true); transferService.transfer(targetName, definition); - } - } - finally + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - startNewTransaction(); - try - { - // Node B6 - faked transfer from repository B. Child of Destination node A1 - NodeRef a1Dest = testNodeFactory.getMappedNodeRef(A1NodeRef); - - assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A1NodeRef))); - assertEquals("dest node A1 From RepositoryId", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A1NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); - assertEquals("dest node A1 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A1NodeRef), TransferModel.PROP_REPOSITORY_ID), repositoryId); - assertEquals("dest node A2 From RepositoryId", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A2NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); - assertEquals("dest node A2 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A2NodeRef), TransferModel.PROP_REPOSITORY_ID), repositoryId); - assertEquals("dest node A3 From RepositoryId", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); - assertEquals("dest node A3 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.PROP_REPOSITORY_ID), repositoryId); - assertEquals("dest node A4 From RepositoryId", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A4NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); - assertEquals("dest node A4 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A4NodeRef), TransferModel.PROP_REPOSITORY_ID), repositoryId); - assertEquals("dest node A5 From RepositoryId", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A5NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); - assertEquals("dest node A5 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A5NodeRef), TransferModel.PROP_REPOSITORY_ID), repositoryId); - - ChildAssociationRef child = nodeService.createNode(a1Dest, ContentModel.ASSOC_CONTAINS, QName.createQName("B6"), ContentModel.TYPE_CONTENT); - B6NodeRef = child.getChildRef(); - nodeService.setProperty(B6NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(B6NodeRef, ContentModel.PROP_NAME, "B6"); - - /** - * The first tranfer was mocked to repository A - this is repository B. - */ - - // This is repository B so there's no need to fake it -// nodeService.setProperty(B6NodeRef, TransferModel.PROP_FROM_REPOSITORY_ID, REPO_ID_B); -// nodeService.setProperty(B6NodeRef, TransferModel.PROP_REPOSITORY_ID, REPO_ID_B); - - ContentWriter writer = contentService.getWriter(B6NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); + @Override + public Void execute() throws Throwable + { + // Node B6 - faked transfer from repository B. Child of Destination node A1 + NodeRef a1Dest = testNodeFactory.getMappedNodeRef(testData.A1NodeRef); - } - finally + assertTrue("dest node A does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A1NodeRef))); + assertEquals("dest node A1 From RepositoryId", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A1NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); + assertEquals("dest node A1 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A1NodeRef), TransferModel.PROP_REPOSITORY_ID), repositoryId); + assertEquals("dest node A2 From RepositoryId", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A2NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); + assertEquals("dest node A2 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A2NodeRef), TransferModel.PROP_REPOSITORY_ID), repositoryId); + assertEquals("dest node A3 From RepositoryId", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); + assertEquals("dest node A3 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.PROP_REPOSITORY_ID), repositoryId); + assertEquals("dest node A4 From RepositoryId", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A4NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); + assertEquals("dest node A4 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A4NodeRef), TransferModel.PROP_REPOSITORY_ID), repositoryId); + assertEquals("dest node A5 From RepositoryId", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); + assertEquals("dest node A5 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), TransferModel.PROP_REPOSITORY_ID), repositoryId); + + ChildAssociationRef child = nodeService.createNode(a1Dest, ContentModel.ASSOC_CONTAINS, QName.createQName("B6"), ContentModel.TYPE_CONTENT); + testData.B6NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B6NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.B6NodeRef, ContentModel.PROP_NAME, "B6"); + + /** + * The first tranfer was mocked to repository A - this is repository B. + */ + + // This is repository B so there's no need to fake it + // nodeService.setProperty(testData.B6NodeRef, TransferModel.PROP_FROM_REPOSITORY_ID, REPO_ID_B); + // nodeService.setProperty(testData.B6NodeRef, TransferModel.PROP_REPOSITORY_ID, REPO_ID_B); + + ContentWriter writer = contentService.getWriter(testData.B6NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - TransferDefinition definition = new TransferDefinition(); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - // Does node B6 still exist ? - assertTrue("dest node B6 does not exist", nodeService.exists(B6NodeRef)); - assertTrue("B6 not alien", nodeService.hasAspect(B6NodeRef, TransferModel.ASPECT_ALIEN)); - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + // Does node B6 still exist ? + assertTrue("dest node B6 does not exist", nodeService.exists(testData.B6NodeRef)); + assertTrue("B6 not alien", nodeService.hasAspect(testData.B6NodeRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); /** Step 2 * Chain Sync * Change Nodes A1 ... A5 source to be received "from repo B" * Create Node A7 - Fake it to be received "from repo B" * transfer */ - String NEW_TITLE="Chain sync"; + final String NEW_TITLE = "Chain sync"; - - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - nodeService.setProperty(A1NodeRef, ContentModel.PROP_TITLE, NEW_TITLE); - nodeService.setProperty(A1NodeRef, TransferModel.PROP_FROM_REPOSITORY_ID, REPO_ID_B); - nodeService.setProperty(A1NodeRef, TransferModel.PROP_REPOSITORY_ID, REPO_ID_B); + @Override + public Void execute() throws Throwable + { + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_TITLE, NEW_TITLE); + nodeService.setProperty(testData.A1NodeRef, TransferModel.PROP_FROM_REPOSITORY_ID, REPO_ID_B); + nodeService.setProperty(testData.A1NodeRef, TransferModel.PROP_REPOSITORY_ID, REPO_ID_B); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_TITLE, NEW_TITLE); - nodeService.setProperty(A2NodeRef, TransferModel.PROP_FROM_REPOSITORY_ID, REPO_ID_B); - nodeService.setProperty(A2NodeRef, TransferModel.PROP_REPOSITORY_ID, REPO_ID_B); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_TITLE, NEW_TITLE); + nodeService.setProperty(testData.A2NodeRef, TransferModel.PROP_FROM_REPOSITORY_ID, REPO_ID_B); + nodeService.setProperty(testData.A2NodeRef, TransferModel.PROP_REPOSITORY_ID, REPO_ID_B); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_TITLE, NEW_TITLE); - nodeService.setProperty(A3NodeRef, TransferModel.PROP_FROM_REPOSITORY_ID, REPO_ID_B); - nodeService.setProperty(A3NodeRef, TransferModel.PROP_REPOSITORY_ID, REPO_ID_B); - - /** - * The repository was mocked to repoistory A. This is repository B - */ - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A7"), ContentModel.TYPE_CONTENT); - A7NodeRef = child.getChildRef(); - nodeService.setProperty(A7NodeRef, ContentModel.PROP_TITLE, NEW_TITLE); - nodeService.setProperty(A7NodeRef, ContentModel.PROP_NAME, "A7"); - nodeService.setProperty(A7NodeRef, ContentModel.PROP_TITLE, NEW_TITLE); - nodeService.setProperty(A7NodeRef, TransferModel.PROP_FROM_REPOSITORY_ID, REPO_ID_B); - nodeService.setProperty(A7NodeRef, TransferModel.PROP_REPOSITORY_ID, REPO_ID_B); - - ContentWriter writer = contentService.getWriter(A3NodeRef, ContentModel.PROP_CONTENT, true); - writer.setLocale(CONTENT_LOCALE); - writer.putContent(CONTENT_STRING); - } - finally + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_TITLE, NEW_TITLE); + nodeService.setProperty(testData.A3NodeRef, TransferModel.PROP_FROM_REPOSITORY_ID, REPO_ID_B); + nodeService.setProperty(testData.A3NodeRef, TransferModel.PROP_REPOSITORY_ID, REPO_ID_B); + + /** + * The repository was mocked to repoistory A. This is repository B + */ + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A7"), ContentModel.TYPE_CONTENT); + testData.A7NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A7NodeRef, ContentModel.PROP_TITLE, NEW_TITLE); + nodeService.setProperty(testData.A7NodeRef, ContentModel.PROP_NAME, "A7"); + nodeService.setProperty(testData.A7NodeRef, ContentModel.PROP_TITLE, NEW_TITLE); + nodeService.setProperty(testData.A7NodeRef, TransferModel.PROP_FROM_REPOSITORY_ID, REPO_ID_B); + nodeService.setProperty(testData.A7NodeRef, TransferModel.PROP_REPOSITORY_ID, REPO_ID_B); + + ContentWriter writer = contentService.getWriter(testData.A3NodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + return null; + } + }); + nodes.add(testData.A7NodeRef); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - nodes.add(A7NodeRef); - - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - TransferDefinition definition = new TransferDefinition(); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally - { - endTransaction(); - } - - try - { - assertTrue("dest node A7 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A7NodeRef))); - - assertEquals("dest node A1 Title", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A1NodeRef), ContentModel.PROP_TITLE), NEW_TITLE); - assertEquals("dest node A1 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A1NodeRef), TransferModel.PROP_REPOSITORY_ID), REPO_ID_B); - assertEquals("dest node A1 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A1NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); - - assertEquals("dest node A2 Title", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A2NodeRef), ContentModel.PROP_TITLE), NEW_TITLE); - assertEquals("dest node A2 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A2NodeRef), TransferModel.PROP_REPOSITORY_ID), REPO_ID_B); - assertEquals("dest node A2 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A2NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); - - assertEquals("dest node A3 Title", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A3NodeRef), ContentModel.PROP_TITLE), NEW_TITLE); - assertEquals("dest node A3 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.PROP_REPOSITORY_ID), REPO_ID_B); - assertEquals("dest node A3 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); - } - finally - { - endTransaction(); - } + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A7 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A7NodeRef))); + + assertEquals("dest node A1 Title", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A1NodeRef), ContentModel.PROP_TITLE), NEW_TITLE); + assertEquals("dest node A1 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A1NodeRef), TransferModel.PROP_REPOSITORY_ID), REPO_ID_B); + assertEquals("dest node A1 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A1NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); + + assertEquals("dest node A2 Title", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A2NodeRef), ContentModel.PROP_TITLE), NEW_TITLE); + assertEquals("dest node A2 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A2NodeRef), TransferModel.PROP_REPOSITORY_ID), REPO_ID_B); + assertEquals("dest node A2 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A2NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); + + assertEquals("dest node A3 Title", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), ContentModel.PROP_TITLE), NEW_TITLE); + assertEquals("dest node A3 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.PROP_REPOSITORY_ID), REPO_ID_B); + assertEquals("dest node A3 Repository Id", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), repositoryId); + return null; + } + }); } // test two repo sync /** @@ -3528,134 +3438,138 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest public void testMultiRepoTransfer() throws Exception { setDefaultRollback(false); - - String CONTENT_TITLE = "ContentTitle"; - String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; - Locale CONTENT_LOCALE = Locale.GERMAN; - String CONTENT_STRING = "Hello"; - - String targetName = "testMultiRepoTransfer"; - TransferTarget transferMe; - NodeRef S0NodeRef; - NodeRef A1NodeRef; - NodeRef A2NodeRef; - NodeRef A3NodeRef; - NodeRef B1NodeRef; - NodeRef C1NodeRef; - NodeRef C2NodeRef; - NodeRef C3NodeRef; - NodeRef C4NodeRef; - NodeRef A3Dummy; - - startNewTransaction(); - try + + final String CONTENT_TITLE = "ContentTitle"; + final String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + final Locale CONTENT_LOCALE = Locale.GERMAN; + final String CONTENT_STRING = "Hello"; + + final String targetName = "testMultiRepoTransfer"; + + class TestData { - /** - * Get guest home - */ - String guestHomeQuery = "/app:company_home/app:guest_home"; - ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); - assertEquals("", 1, guestHomeResult.length()); - NodeRef guestHome = guestHomeResult.getNodeRef(0); - + TransferTarget transferMe; + NodeRef S0NodeRef; + NodeRef A1NodeRef; + NodeRef A2NodeRef; + NodeRef A3NodeRef; + NodeRef B1NodeRef; + NodeRef C1NodeRef; + NodeRef C2NodeRef; + NodeRef C3NodeRef; + NodeRef C4NodeRef; + NodeRef A3Dummy; + } + final TestData testData = new TestData(); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable { /** - * Node Source - located under guest home + * Get guest home */ - String name = GUID.generate(); - ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); - S0NodeRef = child.getChildRef(); - nodeService.setProperty(S0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(S0NodeRef, ContentModel.PROP_NAME, name); + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + { + /** + * Node Source - located under guest home + */ + String name = GUID.generate(); + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); + testData.S0NodeRef = child.getChildRef(); + nodeService.setProperty(testData.S0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.S0NodeRef, ContentModel.PROP_NAME, name); + } + + { + // Node A1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER); + testData.A1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_TITLE, "A1"); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_NAME, "A1"); + } + + { + // Node A2 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("images"), ContentModel.TYPE_FOLDER); + testData.A2NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_TITLE, "images"); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_NAME, "images"); + } + + { + // Node A3 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_FOLDER); + testData.A3NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_TITLE, "A3"); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_NAME, "A3"); + } + + { + // Node B1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B1"), ContentModel.TYPE_FOLDER); + testData.B1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_TITLE, "B1"); + nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_NAME, "B1"); + } + + { + // Node C1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C1"), ContentModel.TYPE_FOLDER); + testData.C1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.C1NodeRef, ContentModel.PROP_TITLE, "C1"); + nodeService.setProperty(testData.C1NodeRef, ContentModel.PROP_NAME, "C1"); + } + + { + // Node C2/images + ChildAssociationRef child = nodeService.createNode(testData.C1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("images"), ContentModel.TYPE_FOLDER); + testData.C2NodeRef = child.getChildRef(); + nodeService.setProperty(testData.C2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.C2NodeRef, ContentModel.PROP_NAME, "images"); + } + + { + // Node C3 + ChildAssociationRef child = nodeService.createNode(testData.C2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C3"), ContentModel.TYPE_FOLDER); + testData.C3NodeRef = child.getChildRef(); + nodeService.setProperty(testData.C3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.C3NodeRef, ContentModel.PROP_NAME, "C3"); + } + + { + // Node A3 (Dummy) + ChildAssociationRef child = nodeService.createNode(testData.C2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_FOLDER); + testData.A3Dummy = child.getChildRef(); + nodeService.setProperty(testData.A3Dummy, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.A3Dummy, ContentModel.PROP_NAME, "A3 Dummy"); + } + + { + // Node C4 + ChildAssociationRef child = nodeService.createNode(testData.A3Dummy, ContentModel.ASSOC_CONTAINS, QName.createQName("C4"), ContentModel.TYPE_FOLDER); + testData.C4NodeRef = child.getChildRef(); + nodeService.setProperty(testData.C4NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.C4NodeRef, ContentModel.PROP_NAME, "C4"); + } + + // Create the transfer target if it does not already exist + if (!transferService.targetExists(targetName)) + { + testData.transferMe = createTransferTarget(targetName); + } + else + { + testData.transferMe = transferService.getTransferTarget(targetName); + } + return null; } - - { - // Node A1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER); - A1NodeRef = child.getChildRef(); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_TITLE, "A1"); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_NAME, "A1"); - } - - { - // Node A2 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("images"), ContentModel.TYPE_FOLDER); - A2NodeRef = child.getChildRef(); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_TITLE, "images"); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_NAME, "images"); - } - - { - // Node A3 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_FOLDER); - A3NodeRef = child.getChildRef(); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_TITLE, "A3"); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_NAME, "A3"); - } - - { - // Node B1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B1"), ContentModel.TYPE_FOLDER); - B1NodeRef = child.getChildRef(); - nodeService.setProperty(B1NodeRef, ContentModel.PROP_TITLE, "B1"); - nodeService.setProperty(B1NodeRef, ContentModel.PROP_NAME, "B1"); - } - - { - // Node C1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C1"), ContentModel.TYPE_FOLDER); - C1NodeRef = child.getChildRef(); - nodeService.setProperty(C1NodeRef, ContentModel.PROP_TITLE, "C1"); - nodeService.setProperty(C1NodeRef, ContentModel.PROP_NAME, "C1"); - } - - { - // Node C2/images - ChildAssociationRef child = nodeService.createNode(C1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("images"), ContentModel.TYPE_FOLDER); - C2NodeRef = child.getChildRef(); - nodeService.setProperty(C2NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(C2NodeRef, ContentModel.PROP_NAME, "images"); - } - - { - // Node C3 - ChildAssociationRef child = nodeService.createNode(C2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C3"), ContentModel.TYPE_FOLDER); - C3NodeRef = child.getChildRef(); - nodeService.setProperty(C3NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(C3NodeRef, ContentModel.PROP_NAME, "C3"); - } - - { - // Node A3 (Dummy) - ChildAssociationRef child = nodeService.createNode(C2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_FOLDER); - A3Dummy = child.getChildRef(); - nodeService.setProperty(A3Dummy, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(A3Dummy, ContentModel.PROP_NAME, "A3 Dummy"); - } - - { - // Node C4 - ChildAssociationRef child = nodeService.createNode(A3Dummy, ContentModel.ASSOC_CONTAINS, QName.createQName("C4"), ContentModel.TYPE_FOLDER); - C4NodeRef = child.getChildRef(); - nodeService.setProperty(C4NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(C4NodeRef, ContentModel.PROP_NAME, "C4"); - } - - // Create the transfer target if it does not already exist - if(!transferService.targetExists(targetName)) - { - transferMe = createTransferTarget(targetName); - } - else - { - transferMe = transferService.getTransferTarget(targetName); - } - } - finally - { - endTransaction(); - } - + }); /** * For unit test * - replace the HTTP transport with the in-process transport @@ -3664,254 +3578,239 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); // Map Project A/images to Project B/images // Map Project C/images to Project A/images - nodeService.getPath(A2NodeRef); - pathMap.add(new Pair(nodeService.getPath(A1NodeRef), nodeService.getPath(B1NodeRef))); - pathMap.add(new Pair(nodeService.getPath(C1NodeRef), nodeService.getPath(B1NodeRef))); - DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_A); - transferServiceImpl.setDescriptorService(mockedDescriptorService); - - /** - * Step 1 - * Now transfer in A's nodes to Repo B - */ - startNewTransaction(); - try + nodeService.getPath(testData.A2NodeRef); + pathMap.add(new Pair(nodeService.getPath(testData.A1NodeRef), nodeService.getPath(testData.B1NodeRef))); + pathMap.add(new Pair(nodeService.getPath(testData.C1NodeRef), nodeService.getPath(testData.B1NodeRef))); { - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(A1NodeRef); - nodes.add(A2NodeRef); - nodes.add(A3NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A2NodeRef))); - assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - - // Check that A3 dest is a child of A2Dest which is a child of B1 - ChildAssociationRef A3Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A3NodeRef)); - assertEquals("A3 dest is connected to the wrong node", A3Ref.getParentRef(), testNodeFactory.getMappedNodeRef(A2NodeRef)); - ChildAssociationRef A2Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A2NodeRef)); - assertEquals("A2 dest is connected to the wrong node", A2Ref.getParentRef(), B1NodeRef); - assertEquals("A2 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A2NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); - assertEquals("A3 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); - } - finally - { - endTransaction(); - } - - /** - * Step 2 - * Now transfer in C's nodes - * B2 (Owned by A) gets invaded by C - */ - startNewTransaction(); - try - { - mockedDescriptorService = getMockDescriptorService(REPO_ID_C); + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_A); transferServiceImpl.setDescriptorService(mockedDescriptorService); - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(C1NodeRef); - nodes.add(C2NodeRef); - nodes.add(C3NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - - // Check that A3 dest is a child of A2Dest which is a child of B1 - // Check that C3 dest is a child of A2Dest - ChildAssociationRef A3Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A3NodeRef)); - assertEquals("A3 dest is connected to the wrong node", A3Ref.getParentRef(), testNodeFactory.getMappedNodeRef(A2NodeRef)); - ChildAssociationRef C3Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A3NodeRef)); - assertEquals("C3 dest is connected to the wrong node", C3Ref.getParentRef(), testNodeFactory.getMappedNodeRef(A2NodeRef)); - ChildAssociationRef A2Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A2NodeRef)); - assertEquals("A2 dest is connected to the wrong node", A2Ref.getParentRef(), B1NodeRef); - - assertTrue("A2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A2NodeRef), TransferModel.ASPECT_ALIEN)); - assertTrue("C3 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C3NodeRef), TransferModel.ASPECT_ALIEN)); - assertFalse("A3 dest is invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.ASPECT_ALIEN)); - - assertEquals("A2 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A2NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); - assertEquals("A3 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); - assertEquals("C3 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(C3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_C); - } - finally - { - endTransaction(); - } - + /** - * Step 3 - * Invade A3Dest via transfer of C4 from C + * Step 1 Now transfer in A's nodes to Repo B */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - mockedDescriptorService = getMockDescriptorService(REPO_ID_C); - transferServiceImpl.setDescriptorService(mockedDescriptorService); - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(C4NodeRef); - definition.setNodes(nodes); - definition.setSync(false); - transferService.transfer(targetName, definition); - } - finally + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.A1NodeRef); + nodes.add(testData.A2NodeRef); + nodes.add(testData.A3NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A2NodeRef))); + assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + + // Check that A3 dest is a child of A2Dest which is a child of B1 + ChildAssociationRef A3Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A3NodeRef)); + assertEquals("A3 dest is connected to the wrong node", A3Ref.getParentRef(), testNodeFactory.getMappedNodeRef(testData.A2NodeRef)); + ChildAssociationRef A2Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A2NodeRef)); + assertEquals("A2 dest is connected to the wrong node", A2Ref.getParentRef(), testData.B1NodeRef); + assertEquals("A2 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A2NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); + assertEquals("A3 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); + return null; + } + }); + /** + * Step 2 Now transfer in C's nodes B2 (Owned by A) gets invaded by C + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - assertTrue("dest node C4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C4NodeRef))); - - assertTrue("C4 is not an invader", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C4NodeRef), TransferModel.ASPECT_ALIEN)); - assertTrue("A3 is not an invader", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.ASPECT_ALIEN)); - - assertEquals("A2 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A2NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); - assertEquals("A3 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); - assertEquals("C3 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(C3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_C); - - } - finally + @Override + public Void execute() throws Throwable + { + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_C); + transferServiceImpl.setDescriptorService(mockedDescriptorService); + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.C1NodeRef); + nodes.add(testData.C2NodeRef); + nodes.add(testData.C3NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + + // Check that A3 dest is a child of A2Dest which is a child of B1 + // Check that C3 dest is a child of A2Dest + ChildAssociationRef A3Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A3NodeRef)); + assertEquals("A3 dest is connected to the wrong node", A3Ref.getParentRef(), testNodeFactory.getMappedNodeRef(testData.A2NodeRef)); + ChildAssociationRef C3Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A3NodeRef)); + assertEquals("C3 dest is connected to the wrong node", C3Ref.getParentRef(), testNodeFactory.getMappedNodeRef(testData.A2NodeRef)); + ChildAssociationRef A2Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A2NodeRef)); + assertEquals("A2 dest is connected to the wrong node", A2Ref.getParentRef(), testData.B1NodeRef); + + assertTrue("A2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A2NodeRef), TransferModel.ASPECT_ALIEN)); + assertTrue("C3 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C3NodeRef), TransferModel.ASPECT_ALIEN)); + assertFalse("A3 dest is invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.ASPECT_ALIEN)); + + assertEquals("A2 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A2NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); + assertEquals("A3 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); + assertEquals("C3 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.C3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_C); + return null; + } + }); + /** + * Step 3 Invade A3Dest via transfer of C4 from C + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_C); + transferServiceImpl.setDescriptorService(mockedDescriptorService); + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.C4NodeRef); + definition.setNodes(nodes); + definition.setSync(false); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + assertTrue("dest node C4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C4NodeRef))); + + assertTrue("C4 is not an invader", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C4NodeRef), TransferModel.ASPECT_ALIEN)); + assertTrue("A3 is not an invader", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.ASPECT_ALIEN)); + + assertEquals("A2 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A2NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); + assertEquals("A3 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_A); + assertEquals("C3 dest owned by wrong repo", nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.C3NodeRef), TransferModel.PROP_FROM_REPOSITORY_ID), REPO_ID_C); + + return null; + } + }); /** * Step 4 * Uninvade A3 from C by deleting C4 * Via Sync of A3Dummy (which has the same destination path as A3). */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - nodeService.deleteNode(C4NodeRef); - - } - finally - { - endTransaction(); - } - startNewTransaction(); - try - { - mockedDescriptorService = getMockDescriptorService(REPO_ID_C); - transferServiceImpl.setDescriptorService(mockedDescriptorService); - - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(A3Dummy); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - assertFalse("dest node C4 not deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(C4NodeRef))); - - logger.debug("A3 Dest is " + testNodeFactory.getMappedNodeRef(A3NodeRef)); - assertFalse("A3 Dest still invaded by C4", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.ASPECT_ALIEN)); - } - finally - { - endTransaction(); - } - - /** - * Step 5 - repeat the above test with transfer(non sync) rather than transfer(sync) - * Uninvade by deleting C3. - */ - startNewTransaction(); - try - { - nodeService.deleteNode(C3NodeRef); - - } - finally - { - endTransaction(); - } - startNewTransaction(); - try - { - mockedDescriptorService = getMockDescriptorService(REPO_ID_C); - transferServiceImpl.setDescriptorService(mockedDescriptorService); - - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - - NodeRef C3Deleted = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, C3NodeRef.getId()); - nodes.add(C3Deleted); - - definition.setNodes(nodes); - definition.setSync(false); - transferService.transfer(targetName, definition); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A3NodeRef))); - assertFalse("dest node C3 not deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - assertFalse("dest node C4 not deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(C4NodeRef))); - assertFalse("A3 still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A3NodeRef), TransferModel.ASPECT_ALIEN)); - assertFalse("A2 still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A2NodeRef), TransferModel.ASPECT_ALIEN)); - } - finally - { - endTransaction(); - } + @Override + public Void execute() throws Throwable + { + nodeService.deleteNode(testData.C4NodeRef); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_C); + transferServiceImpl.setDescriptorService(mockedDescriptorService); + + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.A3Dummy); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + assertFalse("dest node C4 not deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C4NodeRef))); + + logger.debug("A3 Dest is " + testNodeFactory.getMappedNodeRef(testData.A3NodeRef)); + assertFalse("A3 Dest still invaded by C4", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.ASPECT_ALIEN)); + return null; + } + }); + /** + * Step 5 - repeat the above test with transfer(non sync) rather than transfer(sync) Uninvade by deleting C3. + */ + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + nodeService.deleteNode(testData.C3NodeRef); + + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_C); + transferServiceImpl.setDescriptorService(mockedDescriptorService); + + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + + NodeRef C3Deleted = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, testData.C3NodeRef.getId()); + nodes.add(C3Deleted); + + definition.setNodes(nodes); + definition.setSync(false); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A3NodeRef))); + assertFalse("dest node C3 not deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + assertFalse("dest node C4 not deleted", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C4NodeRef))); + assertFalse("A3 still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A3NodeRef), TransferModel.ASPECT_ALIEN)); + assertFalse("A2 still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A2NodeRef), TransferModel.ASPECT_ALIEN)); + return null; + } + }); } // test multi repo sync - - // Utility methods below. private TransferTarget createTransferTarget(String name) - { + { String title = "title"; String description = "description"; String endpointProtocol = "http"; @@ -3920,14 +3819,14 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest String endpointPath = "/alfresco/service/api/transfer"; String username = "admin"; char[] password = "admin".toCharArray(); - + /** * Now go ahead and create our first transfer target */ TransferTarget target = transferService.createAndSaveTransferTarget(name, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); return target; } - + /** * Test the transfer method behaviour with respect to move and alien nodes. * @@ -3966,139 +3865,142 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest public void testMultiRepoTransferMove() throws Exception { setDefaultRollback(false); - + final String localRepositoryId = descriptorService.getCurrentRepositoryDescriptor().getId(); - - String CONTENT_TITLE = "ContentTitle"; - String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; - Locale CONTENT_LOCALE = Locale.GERMAN; - String CONTENT_STRING = "Hello"; - - String targetName = "testMultiRepoTransferMove"; - TransferTarget transferMe; - NodeRef S0NodeRef; - NodeRef A1NodeRef; - NodeRef B1NodeRef; - NodeRef C1NodeRef; - NodeRef C2NodeRef; - NodeRef C3NodeRef; - NodeRef A4NodeRef; - NodeRef A5NodeRef; - NodeRef B6NodeRef; - NodeRef C2DummyNodeRef; - NodeRef C3DummyNodeRef; - QName C2Path = QName.createQName("p2"); - QName C3Path= QName.createQName("p3"); - - startNewTransaction(); - try + + final String CONTENT_TITLE = "ContentTitle"; + final String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + final Locale CONTENT_LOCALE = Locale.GERMAN; + final String CONTENT_STRING = "Hello"; + + final String targetName = "testMultiRepoTransferMove"; + class TestData { - /** - * Get guest home - */ - String guestHomeQuery = "/app:company_home/app:guest_home"; - ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); - assertEquals("", 1, guestHomeResult.length()); - NodeRef guestHome = guestHomeResult.getNodeRef(0); - + TransferTarget transferMe; + NodeRef S0NodeRef; + NodeRef A1NodeRef; + NodeRef B1NodeRef; + NodeRef C1NodeRef; + NodeRef C2NodeRef; + NodeRef C3NodeRef; + NodeRef A4NodeRef; + NodeRef A5NodeRef; + NodeRef B6NodeRef; + NodeRef C2DummyNodeRef; + NodeRef C3DummyNodeRef; + } + final TestData testData = new TestData(); + final QName C2Path = QName.createQName("p2"); + final QName C3Path = QName.createQName("p3"); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable { /** - * Node Source - located under guest home + * Get guest home */ - String name = GUID.generate(); - ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); - S0NodeRef = child.getChildRef(); - nodeService.setProperty(S0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(S0NodeRef, ContentModel.PROP_NAME, name); + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + { + /** + * Node Source - located under guest home + */ + String name = GUID.generate(); + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); + testData.S0NodeRef = child.getChildRef(); + nodeService.setProperty(testData.S0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.S0NodeRef, ContentModel.PROP_NAME, name); + } + + { + // Node A1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER); + testData.A1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_TITLE, "A1"); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_NAME, "A1"); + } + + { + // Node B1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B1"), ContentModel.TYPE_FOLDER); + testData.B1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_TITLE, "B1"); + nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_NAME, "B1"); + } + + { + // Node C1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C1"), ContentModel.TYPE_FOLDER); + testData.C1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.C1NodeRef, ContentModel.PROP_TITLE, "C1"); + nodeService.setProperty(testData.C1NodeRef, ContentModel.PROP_NAME, "C1"); + } + + { + // Node C2 + ChildAssociationRef child = nodeService.createNode(testData.C1NodeRef, ContentModel.ASSOC_CONTAINS, C2Path, ContentModel.TYPE_FOLDER); + testData.C2NodeRef = child.getChildRef(); + nodeService.setProperty(testData.C2NodeRef, ContentModel.PROP_TITLE, "C2"); + nodeService.setProperty(testData.C2NodeRef, ContentModel.PROP_NAME, "C2"); + } + + { + // Node C3 + ChildAssociationRef child = nodeService.createNode(testData.C1NodeRef, ContentModel.ASSOC_CONTAINS, C3Path, ContentModel.TYPE_FOLDER); + testData.C3NodeRef = child.getChildRef(); + nodeService.setProperty(testData.C3NodeRef, ContentModel.PROP_TITLE, "C3"); + nodeService.setProperty(testData.C3NodeRef, ContentModel.PROP_NAME, "C3"); + } + + { + // Node C2 (Dummy) + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, C2Path, ContentModel.TYPE_FOLDER); + testData.C2DummyNodeRef = child.getChildRef(); + nodeService.setProperty(testData.C2DummyNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.C2DummyNodeRef, ContentModel.PROP_NAME, "C2 Dummy"); + } + + { + // Node C3 (Dummy) + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, C3Path, ContentModel.TYPE_FOLDER); + testData.C3DummyNodeRef = child.getChildRef(); + nodeService.setProperty(testData.C3DummyNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.C3DummyNodeRef, ContentModel.PROP_NAME, "C3 Dummy"); + } + + { + // Node A4 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C4"), ContentModel.TYPE_FOLDER); + testData.A4NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_TITLE, "A4"); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_NAME, "A4"); + } + + { + // Node A5 + ChildAssociationRef child = nodeService.createNode(testData.C2DummyNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_FOLDER); + testData.A5NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_TITLE, "A5"); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_NAME, "A5"); + } + + // Create the transfer target if it does not already exist + if (!transferService.targetExists(targetName)) + { + testData.transferMe = createTransferTarget(targetName); + } + else + { + testData.transferMe = transferService.getTransferTarget(targetName); + } + return null; } - - { - // Node A1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER); - A1NodeRef = child.getChildRef(); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_TITLE, "A1"); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_NAME, "A1"); - } - - { - // Node B1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B1"), ContentModel.TYPE_FOLDER); - B1NodeRef = child.getChildRef(); - nodeService.setProperty(B1NodeRef, ContentModel.PROP_TITLE, "B1"); - nodeService.setProperty(B1NodeRef, ContentModel.PROP_NAME, "B1"); - } - - { - // Node C1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C1"), ContentModel.TYPE_FOLDER); - C1NodeRef = child.getChildRef(); - nodeService.setProperty(C1NodeRef, ContentModel.PROP_TITLE, "C1"); - nodeService.setProperty(C1NodeRef, ContentModel.PROP_NAME, "C1"); - } - - { - // Node C2 - ChildAssociationRef child = nodeService.createNode(C1NodeRef, ContentModel.ASSOC_CONTAINS, C2Path, ContentModel.TYPE_FOLDER); - C2NodeRef = child.getChildRef(); - nodeService.setProperty(C2NodeRef, ContentModel.PROP_TITLE, "C2"); - nodeService.setProperty(C2NodeRef, ContentModel.PROP_NAME, "C2"); - } - - { - // Node C3 - ChildAssociationRef child = nodeService.createNode(C1NodeRef, ContentModel.ASSOC_CONTAINS, C3Path, ContentModel.TYPE_FOLDER); - C3NodeRef = child.getChildRef(); - nodeService.setProperty(C3NodeRef, ContentModel.PROP_TITLE, "C3"); - nodeService.setProperty(C3NodeRef, ContentModel.PROP_NAME, "C3"); - } - - { - // Node C2 (Dummy) - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, C2Path, ContentModel.TYPE_FOLDER); - C2DummyNodeRef = child.getChildRef(); - nodeService.setProperty(C2DummyNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(C2DummyNodeRef, ContentModel.PROP_NAME, "C2 Dummy"); - } - - { - // Node C3 (Dummy) - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, C3Path, ContentModel.TYPE_FOLDER); - C3DummyNodeRef = child.getChildRef(); - nodeService.setProperty(C3DummyNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(C3DummyNodeRef, ContentModel.PROP_NAME, "C3 Dummy"); - } - - { - // Node A4 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C4"), ContentModel.TYPE_FOLDER); - A4NodeRef = child.getChildRef(); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_TITLE, "A4"); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_NAME, "A4"); - } - - { - // Node A5 - ChildAssociationRef child = nodeService.createNode(C2DummyNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_FOLDER); - A5NodeRef = child.getChildRef(); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_TITLE, "A5"); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_NAME, "A5"); - } - - // Create the transfer target if it does not already exist - if(!transferService.targetExists(targetName)) - { - transferMe = createTransferTarget(targetName); - } - else - { - transferMe = transferService.getTransferTarget(targetName); - } - } - finally - { - endTransaction(); - } - + }); /** * For unit test * - replace the HTTP transport with the in-process transport @@ -4107,266 +4009,253 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); // Map Project A to Project B // Map Project C to Project B - pathMap.add(new Pair(nodeService.getPath(A1NodeRef), nodeService.getPath(B1NodeRef))); - pathMap.add(new Pair(nodeService.getPath(C1NodeRef), nodeService.getPath(B1NodeRef))); - + pathMap.add(new Pair(nodeService.getPath(testData.A1NodeRef), nodeService.getPath(testData.B1NodeRef))); + pathMap.add(new Pair(nodeService.getPath(testData.C1NodeRef), nodeService.getPath(testData.B1NodeRef))); + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_C); transferServiceImpl.setDescriptorService(mockedDescriptorService); - + /** * Step 1 * Now transfer in C's nodes to Repo B */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(C1NodeRef); - nodes.add(C2NodeRef); - nodes.add(C3NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.C1NodeRef); + nodes.add(testData.C2NodeRef); + nodes.add(testData.C3NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C2NodeRef))); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - - // Check that C3 dest is a child of B1 - ChildAssociationRef C3Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(C3NodeRef)); - assertEquals("A3 dest is connected to the wrong node", C3Ref.getParentRef(), B1NodeRef); - ChildAssociationRef C2Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(C2NodeRef)); - assertEquals("A2 dest is connected to the wrong node", C2Ref.getParentRef(), B1NodeRef); - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C2NodeRef))); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + + // Check that C3 dest is a child of B1 + ChildAssociationRef C3Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.C3NodeRef)); + assertEquals("A3 dest is connected to the wrong node", C3Ref.getParentRef(), testData.B1NodeRef); + ChildAssociationRef C2Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.C2NodeRef)); + assertEquals("A2 dest is connected to the wrong node", C2Ref.getParentRef(), testData.B1NodeRef); + return null; + } + }); mockedDescriptorService = getMockDescriptorService(REPO_ID_A); transferServiceImpl.setDescriptorService(mockedDescriptorService); - + /** * Step 2 * Now transfer in A's nodes * C2 (Dest) gets invaded by A4 */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(A4NodeRef); - nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.A4NodeRef); + nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C2NodeRef))); - ChildAssociationRef A5Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A5NodeRef)); - assertEquals("A5 dest is connected to the wrong node", A5Ref.getParentRef(), testNodeFactory.getMappedNodeRef(C2NodeRef)); - assertTrue("C2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C2NodeRef), TransferModel.ASPECT_ALIEN)); - assertFalse("C3 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C3NodeRef), TransferModel.ASPECT_ALIEN)); - - ChildAssociationRef A4Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A4NodeRef)); - assertEquals("A4 dest is connected to the wrong node", A4Ref.getParentRef(), B1NodeRef); - - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C2NodeRef))); + ChildAssociationRef A5Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A5NodeRef)); + assertEquals("A5 dest is connected to the wrong node", A5Ref.getParentRef(), testNodeFactory.getMappedNodeRef(testData.C2NodeRef)); + assertTrue("C2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C2NodeRef), TransferModel.ASPECT_ALIEN)); + assertFalse("C3 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C3NodeRef), TransferModel.ASPECT_ALIEN)); + + ChildAssociationRef A4Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A4NodeRef)); + assertEquals("A4 dest is connected to the wrong node", A4Ref.getParentRef(), testData.B1NodeRef); + + return null; + } + }); /** * Step 3 * Now move A5 * C3 (Dest) gets invaded by A5 */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - nodeService.moveNode(A5NodeRef, C3DummyNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C4")); - } - finally + @Override + public Void execute() throws Throwable + { + nodeService.moveNode(testData.A5NodeRef, testData.C3DummyNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C4")); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C2NodeRef))); - - // Check that A4 dest is a child of C3Dest which is a child of B1 - ChildAssociationRef A5Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A5NodeRef)); - assertEquals("A5 dest is connected to the wrong node", A5Ref.getParentRef(), testNodeFactory.getMappedNodeRef(C3NodeRef)); - assertTrue("A5 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A5NodeRef), TransferModel.ASPECT_ALIEN)); - assertTrue("C3 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C3NodeRef), TransferModel.ASPECT_ALIEN)); - assertFalse("C2 dest is still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C2NodeRef), TransferModel.ASPECT_ALIEN)); - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C2NodeRef))); + + // Check that A4 dest is a child of C3Dest which is a child of B1 + ChildAssociationRef A5Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A5NodeRef)); + assertEquals("A5 dest is connected to the wrong node", A5Ref.getParentRef(), testNodeFactory.getMappedNodeRef(testData.C3NodeRef)); + assertTrue("A5 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), TransferModel.ASPECT_ALIEN)); + assertTrue("C3 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C3NodeRef), TransferModel.ASPECT_ALIEN)); + assertFalse("C2 dest is still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C2NodeRef), TransferModel.ASPECT_ALIEN)); + return null; + } + }); /** * Step 4 - multi invasion move via transfer service. * Invade A5 by B6. * Transfer from C3 back to C2. */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - nodeService.moveNode(A5NodeRef, C2DummyNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B6")); - - // Node B6 - ChildAssociationRef child = nodeService.createNode(testNodeFactory.getMappedNodeRef(A5NodeRef), ContentModel.ASSOC_CONTAINS, QName.createQName("B6"), ContentModel.TYPE_FOLDER); - B6NodeRef = child.getChildRef(); - nodeService.setProperty(B6NodeRef, ContentModel.PROP_TITLE, "B6"); - nodeService.setProperty(B6NodeRef, ContentModel.PROP_NAME, "B6"); - } - finally + @Override + public Void execute() throws Throwable + { + nodeService.moveNode(testData.A5NodeRef, testData.C2DummyNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B6")); + + // Node B6 + ChildAssociationRef child = nodeService + .createNode(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), ContentModel.ASSOC_CONTAINS, QName.createQName("B6"), ContentModel.TYPE_FOLDER); + testData.B6NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B6NodeRef, ContentModel.PROP_TITLE, "B6"); + nodeService.setProperty(testData.B6NodeRef, ContentModel.PROP_NAME, "B6"); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C2NodeRef))); - - // Check that A4 dest is a child of C2Dest which is a child of B1 - ChildAssociationRef A5Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A5NodeRef)); - ChildAssociationRef B6Ref = nodeService.getPrimaryParent(B6NodeRef); - assertEquals("A5 dest is connected to the wrong node", A5Ref.getParentRef(), testNodeFactory.getMappedNodeRef(C2NodeRef)); - assertEquals("B6 connected to the wrong node", B6Ref.getParentRef(), testNodeFactory.getMappedNodeRef(A5NodeRef)); - assertTrue("A5 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A5NodeRef), TransferModel.ASPECT_ALIEN)); - assertTrue("C2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C2NodeRef), TransferModel.ASPECT_ALIEN)); - assertFalse("C3 dest is still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C3NodeRef), TransferModel.ASPECT_ALIEN)); - Listinvaders = (List)nodeService.getProperty(testNodeFactory.getMappedNodeRef(C2NodeRef), TransferModel.PROP_INVADED_BY); - assertTrue("invaders is too small", invaders.size() > 1); - assertTrue("invaders does not contain REPO A", invaders.contains(REPO_ID_A)); - assertTrue("invaders does not contain REPO B", invaders.contains(localRepositoryId)); - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C2NodeRef))); + + // Check that A4 dest is a child of C2Dest which is a child of B1 + ChildAssociationRef A5Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A5NodeRef)); + ChildAssociationRef B6Ref = nodeService.getPrimaryParent(testData.B6NodeRef); + assertEquals("A5 dest is connected to the wrong node", A5Ref.getParentRef(), testNodeFactory.getMappedNodeRef(testData.C2NodeRef)); + assertEquals("B6 connected to the wrong node", B6Ref.getParentRef(), testNodeFactory.getMappedNodeRef(testData.A5NodeRef)); + assertTrue("A5 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), TransferModel.ASPECT_ALIEN)); + assertTrue("C2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C2NodeRef), TransferModel.ASPECT_ALIEN)); + assertFalse("C3 dest is still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C3NodeRef), TransferModel.ASPECT_ALIEN)); + List invaders = (List) nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.C2NodeRef), TransferModel.PROP_INVADED_BY); + assertTrue("invaders is too small", invaders.size() > 1); + assertTrue("invaders does not contain REPO A", invaders.contains(REPO_ID_A)); + assertTrue("invaders does not contain REPO B", invaders.contains(localRepositoryId)); + return null; + } + }); /** - * Step 5 - * Move + * Step 5 Move */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - nodeService.moveNode(A5NodeRef, A4NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5")); - } - finally + @Override + public Void execute() throws Throwable + { + nodeService.moveNode(testData.A5NodeRef, testData.A4NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5")); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally - { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C2NodeRef))); - - // Check that A5dest is a child of A4Dest which is a child of B1 - ChildAssociationRef A5Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A5NodeRef)); - assertEquals("A5 dest is connected to the wrong node", A5Ref.getParentRef(), testNodeFactory.getMappedNodeRef(A4NodeRef)); - assertTrue("A4 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A4NodeRef), TransferModel.ASPECT_ALIEN)); - assertTrue("A5 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A5NodeRef), TransferModel.ASPECT_ALIEN)); - assertTrue("B6 dest is not invaded", nodeService.hasAspect(B6NodeRef, TransferModel.ASPECT_ALIEN)); - assertFalse("C2 dest is still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C2NodeRef), TransferModel.ASPECT_ALIEN)); - assertFalse("C3 dest is still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C3NodeRef), TransferModel.ASPECT_ALIEN)); - - Listinvaders = (List)nodeService.getProperty(testNodeFactory.getMappedNodeRef(A4NodeRef), TransferModel.PROP_INVADED_BY); - assertTrue("invaders is too big", invaders.size() < 2); - assertFalse("invaders contains REPO A", invaders.contains(REPO_ID_A)); - assertTrue("invaders does not contains REPO B", invaders.contains(REPO_ID_B)); - - } - finally - { - endTransaction(); - } - } - + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C2NodeRef))); + + // Check that A5dest is a child of A4Dest which is a child of B1 + ChildAssociationRef A5Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A5NodeRef)); + assertEquals("A5 dest is connected to the wrong node", A5Ref.getParentRef(), testNodeFactory.getMappedNodeRef(testData.A4NodeRef)); + assertTrue("A4 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A4NodeRef), TransferModel.ASPECT_ALIEN)); + assertTrue("A5 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), TransferModel.ASPECT_ALIEN)); + assertTrue("B6 dest is not invaded", nodeService.hasAspect(testData.B6NodeRef, TransferModel.ASPECT_ALIEN)); + assertFalse("C2 dest is still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C2NodeRef), TransferModel.ASPECT_ALIEN)); + assertFalse("C3 dest is still invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C3NodeRef), TransferModel.ASPECT_ALIEN)); + + List invaders = (List) nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A4NodeRef), TransferModel.PROP_INVADED_BY); + assertTrue("invaders is too big", invaders.size() < 2); + assertFalse("invaders contains REPO A", invaders.contains(REPO_ID_A)); + assertTrue("invaders does not contains REPO B", invaders.contains(REPO_ID_B)); + return null; + } + }); + } + /** * Test the behaviour with regard to copying transferred nodes. *

    @@ -4381,98 +4270,100 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest public void testCopyTransferredNode() throws Exception { setDefaultRollback(false); - - String CONTENT_TITLE = "ContentTitle"; - + + final String CONTENT_TITLE = "ContentTitle"; + /** - * Now go ahead and create our transfer target - */ - String targetName = "testCopyTransferredNode"; - TransferTarget transferMe; - NodeRef S0NodeRef; - NodeRef A1NodeRef; - NodeRef A2NodeRef; - NodeRef A3NodeRef; - NodeRef B1NodeRef; - NodeRef B2NodeRef; - - startNewTransaction(); - try + * Now go ahead and create our transfer target + */ + final String targetName = "testCopyTransferredNode"; + class TestData { - /** - * Get guest home - */ - String guestHomeQuery = "/app:company_home/app:guest_home"; - ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); - assertEquals("", 1, guestHomeResult.length()); - NodeRef guestHome = guestHomeResult.getNodeRef(0); - - /** - * Node Source - located under guest home - */ - { - String name = GUID.generate(); - ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); - S0NodeRef = child.getChildRef(); - nodeService.setProperty(S0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(S0NodeRef, ContentModel.PROP_NAME, name); - } - - { - // Node A1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER); - A1NodeRef = child.getChildRef(); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_TITLE, "A1"); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_NAME, "A1"); - } - - { - // Node A2 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_FOLDER); - A2NodeRef = child.getChildRef(); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_TITLE, "A2"); - nodeService.setProperty(A2NodeRef, ContentModel.PROP_NAME, "A2"); - } - - { - // Node A3 - ChildAssociationRef child = nodeService.createNode(A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_FOLDER); - A3NodeRef = child.getChildRef(); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_TITLE, "A3"); - nodeService.setProperty(A3NodeRef, ContentModel.PROP_NAME, "A3"); - } - - { - // Node B1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B1"), ContentModel.TYPE_FOLDER); - B1NodeRef = child.getChildRef(); - nodeService.setProperty(B1NodeRef, ContentModel.PROP_TITLE, "B1"); - nodeService.setProperty(B1NodeRef, ContentModel.PROP_NAME, "B1"); - } - - { - // Node B2 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B2"), ContentModel.TYPE_FOLDER); - B2NodeRef = child.getChildRef(); - nodeService.setProperty(B1NodeRef, ContentModel.PROP_TITLE, "B2"); - nodeService.setProperty(B1NodeRef, ContentModel.PROP_NAME, "B2"); - } - - - if(!transferService.targetExists(targetName)) - { - transferMe = createTransferTarget(targetName); - } - else - { - transferMe = transferService.getTransferTarget(targetName); - } + TransferTarget transferMe; + NodeRef S0NodeRef; + NodeRef A1NodeRef; + NodeRef A2NodeRef; + NodeRef A3NodeRef; + NodeRef B1NodeRef; + NodeRef B2NodeRef; } - finally + final TestData testData = new TestData(); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + /** + * Node Source - located under guest home + */ + { + String name = GUID.generate(); + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); + testData.S0NodeRef = child.getChildRef(); + nodeService.setProperty(testData.S0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.S0NodeRef, ContentModel.PROP_NAME, name); + } + + { + // Node A1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER); + testData.A1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_TITLE, "A1"); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_NAME, "A1"); + } + + { + // Node A2 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2"), ContentModel.TYPE_FOLDER); + testData.A2NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_TITLE, "A2"); + nodeService.setProperty(testData.A2NodeRef, ContentModel.PROP_NAME, "A2"); + } + + { + // Node A3 + ChildAssociationRef child = nodeService.createNode(testData.A2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A3"), ContentModel.TYPE_FOLDER); + testData.A3NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_TITLE, "A3"); + nodeService.setProperty(testData.A3NodeRef, ContentModel.PROP_NAME, "A3"); + } + + { + // Node B1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B1"), ContentModel.TYPE_FOLDER); + testData.B1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_TITLE, "B1"); + nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_NAME, "B1"); + } + + { + // Node B2 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B2"), ContentModel.TYPE_FOLDER); + testData.B2NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_TITLE, "B2"); + nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_NAME, "B2"); + } + + if (!transferService.targetExists(targetName)) + { + testData.transferMe = createTransferTarget(targetName); + } + else + { + testData.transferMe = transferService.getTransferTarget(targetName); + } + return null; + } + }); /** * For unit test * - replace the HTTP transport with the in-process transport @@ -4480,85 +4371,83 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); - + // Map Project A to Project B - pathMap.add(new Pair(nodeService.getPath(A1NodeRef), nodeService.getPath(B1NodeRef))); - + pathMap.add(new Pair(nodeService.getPath(testData.A1NodeRef), nodeService.getPath(testData.B1NodeRef))); + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_A); transferServiceImpl.setDescriptorService(mockedDescriptorService); - /** * Step 1 */ logger.debug("First transfer - "); - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - /** - * Transfer our transfer target node - */ + @Override + public Void execute() throws Throwable { + /** + * Transfer our transfer target node + */ + { TransferDefinition definition = new TransferDefinition(); - Setnodes = new HashSet(); - nodes.add(A2NodeRef); - nodes.add(A3NodeRef); + Set nodes = new HashSet(); + nodes.add(testData.A2NodeRef); + nodes.add(testData.A3NodeRef); definition.setNodes(nodes); definition.setReadOnly(true); transferService.transfer(targetName, definition); - } - } - finally + } + return null; + } + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try - { - // Now validate that the target node exists with the correct permissions - NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(A2NodeRef); - assertTrue("dest node ref does not exist", nodeService.exists(A2destNodeRef)); - - /** - * Copy the node A2 Dest - */ - NodeRef copiedNode = copyService.copy(A2destNodeRef, B2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2Copy")); - assertTrue("copied node does not exist", nodeService.exists(copiedNode)); - System.out.println("copied node is " + copiedNode); - assertFalse("copied node still has transferred aspect", nodeService.hasAspect(copiedNode, TransferModel.ASPECT_TRANSFERRED)); - assertNull("copied node still has from repository id", nodeService.getProperty(copiedNode, TransferModel.PROP_FROM_REPOSITORY_ID)); - assertNull("copied node still has original repository id", nodeService.getProperty(copiedNode, TransferModel.PROP_REPOSITORY_ID)); - Set aspects = nodeService.getAspects(copiedNode); - - /** - * Copy a chain of transferred nodes - well A2dest and A3dest - */ - copiedNode = copyService.copy(A2destNodeRef, B2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2Copy2"), true); - assertTrue("copied node does not exist", nodeService.exists(copiedNode)); - System.out.println("copied node is " + copiedNode); - assertFalse("copied node still has transferred aspect", nodeService.hasAspect(copiedNode, TransferModel.ASPECT_TRANSFERRED)); - assertNull("copied node still has from repository id", nodeService.getProperty(copiedNode, TransferModel.PROP_FROM_REPOSITORY_ID)); - assertNull("copied node still has original repository id", nodeService.getProperty(copiedNode, TransferModel.PROP_REPOSITORY_ID)); - - List children = nodeService.getChildAssocs(copiedNode); - for(ChildAssociationRef child : children) + @Override + public Void execute() throws Throwable { - assertFalse("copied node still has transferred aspect", nodeService.hasAspect(child.getChildRef(), TransferModel.ASPECT_TRANSFERRED)); - assertNull("copied node still has from repository id", nodeService.getProperty(child.getChildRef(), TransferModel.PROP_FROM_REPOSITORY_ID)); - assertNull("copied node still has original repository id", nodeService.getProperty(child.getChildRef(), TransferModel.PROP_REPOSITORY_ID)); - } - } - finally - { - endTransaction(); - } - } - - + // Now validate that the target node exists with the correct permissions + NodeRef A2destNodeRef = testNodeFactory.getMappedNodeRef(testData.A2NodeRef); + assertTrue("dest node ref does not exist", nodeService.exists(A2destNodeRef)); + + /** + * Copy the node A2 Dest + */ + NodeRef copiedNode = copyService.copy(A2destNodeRef, testData.B2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2Copy")); + assertTrue("copied node does not exist", nodeService.exists(copiedNode)); + System.out.println("copied node is " + copiedNode); + assertFalse("copied node still has transferred aspect", nodeService.hasAspect(copiedNode, TransferModel.ASPECT_TRANSFERRED)); + assertNull("copied node still has from repository id", nodeService.getProperty(copiedNode, TransferModel.PROP_FROM_REPOSITORY_ID)); + assertNull("copied node still has original repository id", nodeService.getProperty(copiedNode, TransferModel.PROP_REPOSITORY_ID)); + Set aspects = nodeService.getAspects(copiedNode); + + /** + * Copy a chain of transferred nodes - well A2dest and A3dest + */ + copiedNode = copyService.copy(A2destNodeRef, testData.B2NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A2Copy2"), true); + assertTrue("copied node does not exist", nodeService.exists(copiedNode)); + System.out.println("copied node is " + copiedNode); + assertFalse("copied node still has transferred aspect", nodeService.hasAspect(copiedNode, TransferModel.ASPECT_TRANSFERRED)); + assertNull("copied node still has from repository id", nodeService.getProperty(copiedNode, TransferModel.PROP_FROM_REPOSITORY_ID)); + assertNull("copied node still has original repository id", nodeService.getProperty(copiedNode, TransferModel.PROP_REPOSITORY_ID)); + + List children = nodeService.getChildAssocs(copiedNode); + for (ChildAssociationRef child : children) + { + assertFalse("copied node still has transferred aspect", nodeService.hasAspect(child.getChildRef(), TransferModel.ASPECT_TRANSFERRED)); + assertNull("copied node still has from repository id", nodeService.getProperty(child.getChildRef(), TransferModel.PROP_FROM_REPOSITORY_ID)); + assertNull("copied node still has original repository id", nodeService.getProperty(child.getChildRef(), TransferModel.PROP_REPOSITORY_ID)); + } + return null; + } + }); + } + /** * Test the behaviour with respect to copy of alien nodes. * @@ -4608,139 +4497,143 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest public void testCopyAlien() throws Exception { setDefaultRollback(false); - - String CONTENT_TITLE = "ContentTitle"; - String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; - Locale CONTENT_LOCALE = Locale.GERMAN; - String CONTENT_STRING = "Hello"; - - String targetName = "testCopyAlien"; - TransferTarget transferMe; - NodeRef S0NodeRef; - NodeRef A1NodeRef; - NodeRef B1NodeRef; - NodeRef C1NodeRef; - NodeRef C2NodeRef; - NodeRef C3NodeRef; - NodeRef A4NodeRef; - NodeRef A5NodeRef; - NodeRef B6NodeRef; - NodeRef C2DummyNodeRef; - NodeRef C3DummyNodeRef; - QName C2Path = QName.createQName("p2"); - QName C3Path= QName.createQName("p3"); - - final String localRepositoryId = descriptorService.getCurrentRepositoryDescriptor().getId(); - - startNewTransaction(); - try + + final String CONTENT_TITLE = "ContentTitle"; + final String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + final Locale CONTENT_LOCALE = Locale.GERMAN; + final String CONTENT_STRING = "Hello"; + + final String targetName = "testCopyAlien"; + class TestData { - /** - * Get guest home - */ - String guestHomeQuery = "/app:company_home/app:guest_home"; - ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); - assertEquals("", 1, guestHomeResult.length()); - NodeRef guestHome = guestHomeResult.getNodeRef(0); - + TransferTarget transferMe; + NodeRef S0NodeRef; + NodeRef A1NodeRef; + NodeRef B1NodeRef; + NodeRef C1NodeRef; + NodeRef C2NodeRef; + NodeRef C3NodeRef; + NodeRef A4NodeRef; + NodeRef A5NodeRef; + NodeRef B6NodeRef; + NodeRef C2DummyNodeRef; + NodeRef C3DummyNodeRef; + } + final TestData testData = new TestData(); + + final QName C2Path = QName.createQName("p2"); + final QName C3Path = QName.createQName("p3"); + + final String localRepositoryId = descriptorService.getCurrentRepositoryDescriptor().getId(); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable { /** - * Node Source - located under guest home + * Get guest home */ - String name = GUID.generate(); - ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); - S0NodeRef = child.getChildRef(); - nodeService.setProperty(S0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(S0NodeRef, ContentModel.PROP_NAME, name); + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + { + /** + * Node Source - located under guest home + */ + String name = GUID.generate(); + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName(name), ContentModel.TYPE_FOLDER); + testData.S0NodeRef = child.getChildRef(); + nodeService.setProperty(testData.S0NodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.S0NodeRef, ContentModel.PROP_NAME, name); + } + + { + // Node A1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER); + testData.A1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_TITLE, "A1"); + nodeService.setProperty(testData.A1NodeRef, ContentModel.PROP_NAME, "A1"); + } + + { + // Node B1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B1"), ContentModel.TYPE_FOLDER); + testData.B1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_TITLE, "B1"); + nodeService.setProperty(testData.B1NodeRef, ContentModel.PROP_NAME, "B1"); + } + + { + // Node C1 + ChildAssociationRef child = nodeService.createNode(testData.S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C1"), ContentModel.TYPE_FOLDER); + testData.C1NodeRef = child.getChildRef(); + nodeService.setProperty(testData.C1NodeRef, ContentModel.PROP_TITLE, "C1"); + nodeService.setProperty(testData.C1NodeRef, ContentModel.PROP_NAME, "C1"); + } + + { + // Node C2 + ChildAssociationRef child = nodeService.createNode(testData.C1NodeRef, ContentModel.ASSOC_CONTAINS, C2Path, ContentModel.TYPE_FOLDER); + testData.C2NodeRef = child.getChildRef(); + nodeService.setProperty(testData.C2NodeRef, ContentModel.PROP_TITLE, "C2"); + nodeService.setProperty(testData.C2NodeRef, ContentModel.PROP_NAME, "C2"); + } + + { + // Node C3 + ChildAssociationRef child = nodeService.createNode(testData.C1NodeRef, ContentModel.ASSOC_CONTAINS, C3Path, ContentModel.TYPE_FOLDER); + testData.C3NodeRef = child.getChildRef(); + nodeService.setProperty(testData.C3NodeRef, ContentModel.PROP_TITLE, "C3"); + nodeService.setProperty(testData.C3NodeRef, ContentModel.PROP_NAME, "C3"); + } + + { + // Node C2 (Dummy) + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, C2Path, ContentModel.TYPE_FOLDER); + testData.C2DummyNodeRef = child.getChildRef(); + nodeService.setProperty(testData.C2DummyNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.C2DummyNodeRef, ContentModel.PROP_NAME, "C2 Dummy"); + } + + { + // Node C3 (Dummy) + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, C3Path, ContentModel.TYPE_FOLDER); + testData.C3DummyNodeRef = child.getChildRef(); + nodeService.setProperty(testData.C3DummyNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(testData.C3DummyNodeRef, ContentModel.PROP_NAME, "C3 Dummy"); + } + + { + // Node A4 + ChildAssociationRef child = nodeService.createNode(testData.A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C4"), ContentModel.TYPE_FOLDER); + testData.A4NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_TITLE, "A4"); + nodeService.setProperty(testData.A4NodeRef, ContentModel.PROP_NAME, "A4"); + } + + { + // Node A5 + ChildAssociationRef child = nodeService.createNode(testData.C2DummyNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_FOLDER); + testData.A5NodeRef = child.getChildRef(); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_TITLE, "A5"); + nodeService.setProperty(testData.A5NodeRef, ContentModel.PROP_NAME, "A5"); + } + + // Create the transfer target if it does not already exist + if (!transferService.targetExists(targetName)) + { + testData.transferMe = createTransferTarget(targetName); + } + else + { + testData.transferMe = transferService.getTransferTarget(targetName); + } + return null; } - - { - // Node A1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A1"), ContentModel.TYPE_FOLDER); - A1NodeRef = child.getChildRef(); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_TITLE, "A1"); - nodeService.setProperty(A1NodeRef, ContentModel.PROP_NAME, "A1"); - } - - { - // Node B1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("B1"), ContentModel.TYPE_FOLDER); - B1NodeRef = child.getChildRef(); - nodeService.setProperty(B1NodeRef, ContentModel.PROP_TITLE, "B1"); - nodeService.setProperty(B1NodeRef, ContentModel.PROP_NAME, "B1"); - } - - { - // Node C1 - ChildAssociationRef child = nodeService.createNode(S0NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C1"), ContentModel.TYPE_FOLDER); - C1NodeRef = child.getChildRef(); - nodeService.setProperty(C1NodeRef, ContentModel.PROP_TITLE, "C1"); - nodeService.setProperty(C1NodeRef, ContentModel.PROP_NAME, "C1"); - } - - { - // Node C2 - ChildAssociationRef child = nodeService.createNode(C1NodeRef, ContentModel.ASSOC_CONTAINS, C2Path, ContentModel.TYPE_FOLDER); - C2NodeRef = child.getChildRef(); - nodeService.setProperty(C2NodeRef, ContentModel.PROP_TITLE, "C2"); - nodeService.setProperty(C2NodeRef, ContentModel.PROP_NAME, "C2"); - } - - { - // Node C3 - ChildAssociationRef child = nodeService.createNode(C1NodeRef, ContentModel.ASSOC_CONTAINS, C3Path, ContentModel.TYPE_FOLDER); - C3NodeRef = child.getChildRef(); - nodeService.setProperty(C3NodeRef, ContentModel.PROP_TITLE, "C3"); - nodeService.setProperty(C3NodeRef, ContentModel.PROP_NAME, "C3"); - } - - { - // Node C2 (Dummy) - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, C2Path, ContentModel.TYPE_FOLDER); - C2DummyNodeRef = child.getChildRef(); - nodeService.setProperty(C2DummyNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(C2DummyNodeRef, ContentModel.PROP_NAME, "C2 Dummy"); - } - - { - // Node C3 (Dummy) - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, C3Path, ContentModel.TYPE_FOLDER); - C3DummyNodeRef = child.getChildRef(); - nodeService.setProperty(C3DummyNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); - nodeService.setProperty(C3DummyNodeRef, ContentModel.PROP_NAME, "C3 Dummy"); - } - - { - // Node A4 - ChildAssociationRef child = nodeService.createNode(A1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("C4"), ContentModel.TYPE_FOLDER); - A4NodeRef = child.getChildRef(); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_TITLE, "A4"); - nodeService.setProperty(A4NodeRef, ContentModel.PROP_NAME, "A4"); - } - - { - // Node A5 - ChildAssociationRef child = nodeService.createNode(C2DummyNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5"), ContentModel.TYPE_FOLDER); - A5NodeRef = child.getChildRef(); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_TITLE, "A5"); - nodeService.setProperty(A5NodeRef, ContentModel.PROP_NAME, "A5"); - } - - // Create the transfer target if it does not already exist - if(!transferService.targetExists(targetName)) - { - transferMe = createTransferTarget(targetName); - } - else - { - transferMe = transferService.getTransferTarget(targetName); - } - } - finally - { - endTransaction(); - } - + }); /** * For unit test * - replace the HTTP transport with the in-process transport @@ -4749,158 +4642,154 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest */ TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService); transferServiceImpl.setTransmitter(transmitter); - UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); - transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + final UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); List> pathMap = testNodeFactory.getPathMap(); // Map Project A to Project B // Map Project C to Project B - pathMap.add(new Pair(nodeService.getPath(A1NodeRef), nodeService.getPath(B1NodeRef))); - pathMap.add(new Pair(nodeService.getPath(C1NodeRef), nodeService.getPath(B1NodeRef))); - + pathMap.add(new Pair(nodeService.getPath(testData.A1NodeRef), nodeService.getPath(testData.B1NodeRef))); + pathMap.add(new Pair(nodeService.getPath(testData.C1NodeRef), nodeService.getPath(testData.B1NodeRef))); + DescriptorService mockedDescriptorService = getMockDescriptorService(REPO_ID_C); transferServiceImpl.setDescriptorService(mockedDescriptorService); - + /** * Step 1 * Now transfer in C's nodes to Repo B */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(C1NodeRef); - nodes.add(C2NodeRef); - nodes.add(C3NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.C1NodeRef); + nodes.add(testData.C2NodeRef); + nodes.add(testData.C3NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C2NodeRef))); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - - // Check that C3 dest is a child of B1 - ChildAssociationRef C3Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(C3NodeRef)); - assertEquals("A3 dest is connected to the wrong node", C3Ref.getParentRef(), B1NodeRef); - ChildAssociationRef C2Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(C2NodeRef)); - assertEquals("A2 dest is connected to the wrong node", C2Ref.getParentRef(), B1NodeRef); - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C2NodeRef))); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + + // Check that C3 dest is a child of B1 + ChildAssociationRef C3Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.C3NodeRef)); + assertEquals("A3 dest is connected to the wrong node", C3Ref.getParentRef(), testData.B1NodeRef); + ChildAssociationRef C2Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.C2NodeRef)); + assertEquals("A2 dest is connected to the wrong node", C2Ref.getParentRef(), testData.B1NodeRef); + return null; + } + }); mockedDescriptorService = getMockDescriptorService(REPO_ID_A); transferServiceImpl.setDescriptorService(mockedDescriptorService); - + /** * Step 2 * Now transfer in A's nodes * C2 (Dest) gets invaded by A4 */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - TransferDefinition definition = new TransferDefinition(); - Collection nodes = new ArrayList(); - nodes.add(A4NodeRef); - nodes.add(A5NodeRef); - definition.setNodes(nodes); - definition.setSync(true); - transferService.transfer(targetName, definition); - } - finally + @Override + public Void execute() throws Throwable + { + TransferDefinition definition = new TransferDefinition(); + Collection nodes = new ArrayList(); + nodes.add(testData.A4NodeRef); + nodes.add(testData.A5NodeRef); + definition.setNodes(nodes); + definition.setSync(true); + transferService.transfer(targetName, definition); + return null; + } + }); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } - - startNewTransaction(); - try - { - assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A5NodeRef))); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C2NodeRef))); - ChildAssociationRef A5Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A5NodeRef)); - assertEquals("A5 dest is connected to the wrong node", A5Ref.getParentRef(), testNodeFactory.getMappedNodeRef(C2NodeRef)); - assertTrue("C2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C2NodeRef), TransferModel.ASPECT_ALIEN)); - assertFalse("C3 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C3NodeRef), TransferModel.ASPECT_ALIEN)); - - ChildAssociationRef A4Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(A4NodeRef)); - assertEquals("A4 dest is connected to the wrong node", A4Ref.getParentRef(), B1NodeRef); - - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + assertTrue("dest node A5 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A5NodeRef))); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C2NodeRef))); + ChildAssociationRef A5Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A5NodeRef)); + assertEquals("A5 dest is connected to the wrong node", A5Ref.getParentRef(), testNodeFactory.getMappedNodeRef(testData.C2NodeRef)); + assertTrue("C2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C2NodeRef), TransferModel.ASPECT_ALIEN)); + assertFalse("C3 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C3NodeRef), TransferModel.ASPECT_ALIEN)); + + ChildAssociationRef A4Ref = nodeService.getPrimaryParent(testNodeFactory.getMappedNodeRef(testData.A4NodeRef)); + assertEquals("A4 dest is connected to the wrong node", A4Ref.getParentRef(), testData.B1NodeRef); + + return null; + } + }); /** * Step 3. Copy A5 from C2 to C3. * C2Dest should still be invaded by A5 * C3Dest should be invaded by A5(copy) which is now a "B/local" invader. */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - NodeRef copyRef = copyService.copy(testNodeFactory.getMappedNodeRef(A5NodeRef), testNodeFactory.getMappedNodeRef(C3NodeRef), ContentModel.ASSOC_CONTAINS, QName.createQName("A5 Copy 1")); - assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C3NodeRef))); - assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C2NodeRef))); - - assertTrue("A5(copy 1) is not invaded", nodeService.hasAspect(copyRef, TransferModel.ASPECT_ALIEN)); - assertTrue("C3 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C3NodeRef), TransferModel.ASPECT_ALIEN)); - assertTrue("C2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C2NodeRef), TransferModel.ASPECT_ALIEN)); - - List C2invaders = (List) nodeService.getProperty(testNodeFactory.getMappedNodeRef(C2NodeRef), TransferModel.PROP_INVADED_BY); - List C3invaders = (List) nodeService.getProperty(testNodeFactory.getMappedNodeRef(C3NodeRef), TransferModel.PROP_INVADED_BY); - assertTrue("C3 invaders contains local repository Id", C3invaders.contains(localRepositoryId)); - assertFalse("C3 invaders contains REPO_ID_A", C3invaders.contains(REPO_ID_A)); - - assertFalse("C2 invaders contains local repository Id", C2invaders.contains(localRepositoryId)); - assertTrue("C2 invaders contains REPO_ID_A", C2invaders.contains(REPO_ID_A)); - - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + NodeRef copyRef = copyService.copy(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), testNodeFactory.getMappedNodeRef(testData.C3NodeRef), ContentModel.ASSOC_CONTAINS, QName + .createQName("A5 Copy 1")); + assertTrue("dest node C3 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C3NodeRef))); + assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C2NodeRef))); + + assertTrue("A5(copy 1) is not invaded", nodeService.hasAspect(copyRef, TransferModel.ASPECT_ALIEN)); + assertTrue("C3 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C3NodeRef), TransferModel.ASPECT_ALIEN)); + assertTrue("C2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C2NodeRef), TransferModel.ASPECT_ALIEN)); + + List C2invaders = (List) nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.C2NodeRef), TransferModel.PROP_INVADED_BY); + List C3invaders = (List) nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.C3NodeRef), TransferModel.PROP_INVADED_BY); + assertTrue("C3 invaders contains local repository Id", C3invaders.contains(localRepositoryId)); + assertFalse("C3 invaders contains REPO_ID_A", C3invaders.contains(REPO_ID_A)); + + assertFalse("C2 invaders contains local repository Id", C2invaders.contains(localRepositoryId)); + assertTrue("C2 invaders contains REPO_ID_A", C2invaders.contains(REPO_ID_A)); + + return null; + } + }); /** * Step 4. Copy A5 from C2 to A4. * C2Dest should still be invaded by A5 * A4Dest should be invaded by A5(copy 2) which is now a "B" invader. */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - NodeRef copyRef = copyService.copy(testNodeFactory.getMappedNodeRef(A5NodeRef), testNodeFactory.getMappedNodeRef(A4NodeRef), ContentModel.ASSOC_CONTAINS, QName.createQName("A5 Copy 2")); - assertTrue("dest node A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(A4NodeRef))); - assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(C2NodeRef))); - - assertTrue("A5(copy 2) is not invaded", nodeService.hasAspect(copyRef, TransferModel.ASPECT_ALIEN)); - assertTrue("A4 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A4NodeRef), TransferModel.ASPECT_ALIEN)); - assertTrue("C2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(C2NodeRef), TransferModel.ASPECT_ALIEN)); - - List C2invaders = (List) nodeService.getProperty(testNodeFactory.getMappedNodeRef(C2NodeRef), TransferModel.PROP_INVADED_BY); - List A4invaders = (List) nodeService.getProperty(testNodeFactory.getMappedNodeRef(A4NodeRef), TransferModel.PROP_INVADED_BY); - assertTrue("A4 invaders contains local repository Id", A4invaders.contains(localRepositoryId)); - - assertFalse("C2 invaders contains local repository Id", C2invaders.contains(localRepositoryId)); - assertTrue("C2 invaders contains REPO_ID_A", C2invaders.contains(REPO_ID_A)); - - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + NodeRef copyRef = copyService.copy(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), testNodeFactory.getMappedNodeRef(testData.A4NodeRef), ContentModel.ASSOC_CONTAINS, QName + .createQName("A5 Copy 2")); + assertTrue("dest node A4 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.A4NodeRef))); + assertTrue("dest node C2 does not exist", nodeService.exists(testNodeFactory.getMappedNodeRef(testData.C2NodeRef))); + + assertTrue("A5(copy 2) is not invaded", nodeService.hasAspect(copyRef, TransferModel.ASPECT_ALIEN)); + assertTrue("A4 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A4NodeRef), TransferModel.ASPECT_ALIEN)); + assertTrue("C2 dest is not invaded", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.C2NodeRef), TransferModel.ASPECT_ALIEN)); + + List C2invaders = (List) nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.C2NodeRef), TransferModel.PROP_INVADED_BY); + List A4invaders = (List) nodeService.getProperty(testNodeFactory.getMappedNodeRef(testData.A4NodeRef), TransferModel.PROP_INVADED_BY); + assertTrue("A4 invaders contains local repository Id", A4invaders.contains(localRepositoryId)); + + assertFalse("C2 invaders contains local repository Id", C2invaders.contains(localRepositoryId)); + assertTrue("C2 invaders contains REPO_ID_A", C2invaders.contains(REPO_ID_A)); + + return null; + } + }); /** * Step 5. Invade A5 dest with B6. * Copy A5(Dest) to B1 (A5 Copy 3) no children @@ -4908,26 +4797,26 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest * A5 Copy 3 not invaded. * B6 Copy not invaded. */ - startNewTransaction(); - try + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - ChildAssociationRef child = nodeService.createNode(testNodeFactory.getMappedNodeRef(A5NodeRef), ContentModel.ASSOC_CONTAINS, QName.createQName("B6"), ContentModel.TYPE_FOLDER); - B6NodeRef = child.getChildRef(); - nodeService.setProperty(B6NodeRef, ContentModel.PROP_TITLE, "B6"); - nodeService.setProperty(B6NodeRef, ContentModel.PROP_NAME, "B6"); - - assertTrue("A4 dest is not invaded prior to test - test error", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(A4NodeRef), TransferModel.ASPECT_ALIEN)); - - NodeRef copyRef = copyService.copy(testNodeFactory.getMappedNodeRef(A5NodeRef), B1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5 Copy 3")); - - assertFalse("B1 is invaded", nodeService.hasAspect(B1NodeRef, TransferModel.ASPECT_ALIEN)); - assertFalse("A5 copy 3 is invaded", nodeService.hasAspect(copyRef, TransferModel.ASPECT_ALIEN)); - } - finally - { - endTransaction(); - } - + @Override + public Void execute() throws Throwable + { + ChildAssociationRef child = nodeService + .createNode(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), ContentModel.ASSOC_CONTAINS, QName.createQName("B6"), ContentModel.TYPE_FOLDER); + testData.B6NodeRef = child.getChildRef(); + nodeService.setProperty(testData.B6NodeRef, ContentModel.PROP_TITLE, "B6"); + nodeService.setProperty(testData.B6NodeRef, ContentModel.PROP_NAME, "B6"); + + assertTrue("A4 dest is not invaded prior to test - test error", nodeService.hasAspect(testNodeFactory.getMappedNodeRef(testData.A4NodeRef), TransferModel.ASPECT_ALIEN)); + + NodeRef copyRef = copyService.copy(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), testData.B1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5 Copy 3")); + + assertFalse("B1 is invaded", nodeService.hasAspect(testData.B1NodeRef, TransferModel.ASPECT_ALIEN)); + assertFalse("A5 copy 3 is invaded", nodeService.hasAspect(copyRef, TransferModel.ASPECT_ALIEN)); + return null; + } + }); /** * Step 6. Invade A5 dest with B6. * Copy A5(Dest) to B1 (A5 Copy 3) with children @@ -4935,56 +4824,56 @@ public class TransferServiceToBeRefactoredTest extends BaseAlfrescoSpringTest * A5 Copy 4 not invaded. * B6 Copy not invaded. */ - startNewTransaction(); - try - { - assertFalse("B1 is invaded prior to test - test error", nodeService.hasAspect(B1NodeRef, TransferModel.ASPECT_ALIEN)); - - NodeRef copyRef = copyService.copy(testNodeFactory.getMappedNodeRef(A5NodeRef), B1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5 Copy 4"), true); - assertFalse("B1 is invaded", nodeService.hasAspect(B1NodeRef, TransferModel.ASPECT_ALIEN)); - assertFalse("A5 copy 4 is invaded", nodeService.hasAspect(copyRef, TransferModel.ASPECT_ALIEN)); - - List refs = nodeService.getChildAssocs(copyRef); - - assertTrue("can't find child of A5 copy 4", refs.size() == 1); - - for(ChildAssociationRef ref : refs) - { - assertFalse("B6 copy is invaded", nodeService.hasAspect(ref.getChildRef(), TransferModel.ASPECT_ALIEN)); - } - } - finally + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - endTransaction(); - } + @Override + public Void execute() throws Throwable + { + assertFalse("B1 is invaded prior to test - test error", nodeService.hasAspect(testData.B1NodeRef, TransferModel.ASPECT_ALIEN)); + + NodeRef copyRef = copyService.copy(testNodeFactory.getMappedNodeRef(testData.A5NodeRef), testData.B1NodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("A5 Copy 4"), true); + assertFalse("B1 is invaded", nodeService.hasAspect(testData.B1NodeRef, TransferModel.ASPECT_ALIEN)); + assertFalse("A5 copy 4 is invaded", nodeService.hasAspect(copyRef, TransferModel.ASPECT_ALIEN)); + + List refs = nodeService.getChildAssocs(copyRef); + + assertTrue("can't find child of A5 copy 4", refs.size() == 1); + + for (ChildAssociationRef ref : refs) + { + assertFalse("B6 copy is invaded", nodeService.hasAspect(ref.getChildRef(), TransferModel.ASPECT_ALIEN)); + } + return null; + } + }); } // copy node - - + private void createUser(String userName, String password) { if (this.authenticationService.authenticationExists(userName) == false) { this.authenticationService.createAuthentication(userName, password.toCharArray()); - + PropertyMap ppOne = new PropertyMap(4); ppOne.put(ContentModel.PROP_USERNAME, userName); ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName"); ppOne.put(ContentModel.PROP_LASTNAME, "lastName"); ppOne.put(ContentModel.PROP_EMAIL, "email@email.com"); ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle"); - + this.personService.createPerson(ppOne); - } + } } - + private DescriptorService getMockDescriptorService(String repositoryId) { DescriptorService descriptorService = mock(DescriptorService.class); Descriptor descriptor = mock(Descriptor.class); - + when(descriptor.getId()).thenReturn(repositoryId); when(descriptorService.getCurrentRepositoryDescriptor()).thenReturn(descriptor); - + when(descriptorService.getServerDescriptor()).thenReturn(serverDescriptor); + return descriptorService; - } + } } diff --git a/source/java/org/alfresco/repo/transfer/TransferTransmitter.java b/source/java/org/alfresco/repo/transfer/TransferTransmitter.java index 98d68734e8..cf5c182b46 100644 --- a/source/java/org/alfresco/repo/transfer/TransferTransmitter.java +++ b/source/java/org/alfresco/repo/transfer/TransferTransmitter.java @@ -27,6 +27,7 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.transfer.TransferException; import org.alfresco.service.cmr.transfer.TransferProgress; import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.service.cmr.transfer.TransferVersion; /** * @author brian @@ -47,10 +48,11 @@ public interface TransferTransmitter * * @param target definition of where to transfer to. * @param fromRepositoryId the repositoryID of the sending system + * @param fromVersion the version of the repository sending * @return the transfer object or null if the target cannot be locked. * @throws TransferException */ - Transfer begin(TransferTarget target, String fromRepositoryId) throws TransferException; + Transfer begin(TransferTarget target, String fromRepositoryId, TransferVersion fromVersion) throws TransferException; /** * @param manifest, the transfer manifest file diff --git a/source/java/org/alfresco/repo/transfer/TransferVersionChecker.java b/source/java/org/alfresco/repo/transfer/TransferVersionChecker.java new file mode 100644 index 0000000000..5e9e4127a0 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferVersionChecker.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.transfer; + +import org.alfresco.service.cmr.transfer.TransferVersion; + +/** + * + * @author mrogers + * + */ +public interface TransferVersionChecker +{ + /** + * Checks whether transfer is compatible between the two versions + * @param from the version from + * @param to the version to + * @return boolean true the versions are compatible + */ + boolean checkTransferVersions(TransferVersion from, TransferVersion to); + +} diff --git a/source/java/org/alfresco/repo/transfer/TransferVersionCheckerImpl.java b/source/java/org/alfresco/repo/transfer/TransferVersionCheckerImpl.java new file mode 100644 index 0000000000..ff3926d5d8 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferVersionCheckerImpl.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.transfer; + +import org.alfresco.service.cmr.transfer.TransferVersion; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This is an implementation of TransferVersionChecker. + * + * It allows transfer to the same edition/major/minor but ignores revision. + */ +public class TransferVersionCheckerImpl implements TransferVersionChecker +{ + private static Log logger = LogFactory.getLog(TransferVersionCheckerImpl.class); + + public boolean checkTransferVersions(TransferVersion from, TransferVersion to) + { + logger.debug("checkTransferVersions from:" + from + ", to:" + to); + + if(from == null || to == null || to.getEdition() == null || to.getVersionMajor() == null || to.getVersionMinor() == null) + { + return false; + } + + if(!from.getEdition().equalsIgnoreCase(to.getEdition())) + { + return false; + } + + if(!from.getVersionMajor().equalsIgnoreCase(to.getVersionMajor())) + { + return false; + } + + if(!from.getVersionMinor().equalsIgnoreCase(to.getVersionMinor())) + { + return false; + } + + // ignore revisions + + return true; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/TransferVersionCheckerImplTest.java b/source/java/org/alfresco/repo/transfer/TransferVersionCheckerImplTest.java new file mode 100644 index 0000000000..1311b106e7 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferVersionCheckerImplTest.java @@ -0,0 +1,43 @@ +package org.alfresco.repo.transfer; + +import org.alfresco.service.cmr.transfer.TransferVersion; +import org.alfresco.util.BaseAlfrescoSpringTest; + +/** + * Unit test for TransferVersionChecker + * @author mrogers + */ +public class TransferVersionCheckerImplTest extends BaseAlfrescoSpringTest +{ + /** + * Test TransferVersionCheckerImpl + */ + public void testTransferVersionCheckerImpl() + { + + TransferVersionChecker checker = new TransferVersionCheckerImpl(); + + String EDITION = "Ent"; + + TransferVersion e = new TransferVersionImpl("3", "3", "0", EDITION); + TransferVersion e2 = new TransferVersionImpl("3", "3", "0", EDITION); + TransferVersion e3 = new TransferVersionImpl("3", "3", "1", EDITION); + + assertTrue(e.equals(e2)); + assertFalse(e.equals(e3)); + + assertTrue("same object equals", checker.checkTransferVersions(e, e)); + assertTrue("duplicate object equals", checker.checkTransferVersions(e, e2)); + + // The revision should be ignored + assertTrue("not equals", checker.checkTransferVersions(e, new TransferVersionImpl("3", "3", "1", EDITION))); + assertTrue("not equals", checker.checkTransferVersions(e, new TransferVersionImpl("3", "3", "2", EDITION))); + + // These should not match + assertFalse("not equals minor different", checker.checkTransferVersions(e, new TransferVersionImpl("3", "4", "0", EDITION))); + assertFalse("not equals major different", checker.checkTransferVersions(e, new TransferVersionImpl("4", "3", "0", EDITION))); + assertFalse("not equals edition different", checker.checkTransferVersions(e, new TransferVersionImpl("3", "3", "0", "Whatever"))); + assertFalse("not equals edition null ", checker.checkTransferVersions(e, new TransferVersionImpl("3", "3", "0", null))); + } + +} diff --git a/source/java/org/alfresco/repo/transfer/TransferVersionCheckerNoOp.java b/source/java/org/alfresco/repo/transfer/TransferVersionCheckerNoOp.java new file mode 100644 index 0000000000..1ef3300370 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferVersionCheckerNoOp.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.transfer; + +import org.alfresco.service.cmr.transfer.TransferVersion; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This implementation of TransferVersionChecker simply allows transfer between all versions. + */ +public class TransferVersionCheckerNoOp implements TransferVersionChecker +{ + private static Log logger = LogFactory.getLog(TransferVersionCheckerNoOp.class); + + public boolean checkTransferVersions(TransferVersion from, TransferVersion to) + { + logger.debug("checkTransferVersions from:" + from + ", to:" + to); + return true; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/TransferVersionImpl.java b/source/java/org/alfresco/repo/transfer/TransferVersionImpl.java new file mode 100644 index 0000000000..5835f35c66 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferVersionImpl.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.transfer; + +import org.alfresco.service.cmr.transfer.TransferVersion; +import org.alfresco.service.descriptor.Descriptor; + +public class TransferVersionImpl implements TransferVersion +{ + private String versionMajor; + private String versionMinor; + private String versionRevision; + private String edition; + + /** + * + * @param versionMajor + * @param versionMinor + * @param versionRevision + * @param edition + */ + public TransferVersionImpl(String versionMajor, String versionMinor, String versionRevision, String edition) + { + this.versionMajor = versionMajor; + this.versionMinor = versionMinor; + this.versionRevision = versionRevision; + this.edition = edition; + } + + /** + * Construct a transferVersion from a system descriptor + * @param d the system descriptor + */ + public TransferVersionImpl(Descriptor d) + { + this.versionMajor = d.getVersionMajor(); + this.versionMinor = d.getVersionMinor(); + this.versionRevision = d.getVersionRevision(); + this.edition = d.getEdition(); + } + + @Override + public String getVersionMajor() + { + return versionMajor; + } + + @Override + public String getVersionMinor() + { + return versionMinor; + } + + @Override + public String getVersionRevision() + { + return versionRevision; + } + + @Override + public String getEdition() + { + return edition; + } + + public String toString() + { + StringBuilder version = new StringBuilder(); + version.append(getEdition()); + version.append("."); + version.append(getVersionMajor()); + version.append("."); + version.append(getVersionMinor()); + version.append("."); + version.append(getVersionRevision()); + + return version.toString(); + } + + public int hashCode() + { + if(edition != null && versionMinor != null) + { + return edition.hashCode() + versionMinor.hashCode() * 37; + } + else + { + return 1; + } + } + + public boolean equals(Object other) + { + if(other == null) + { + return false; + } + + if (other instanceof TransferVersion) + { + TransferVersion v = (TransferVersion)other; + + if(!edition.equalsIgnoreCase(v.getEdition())) + { + return false; + } + if(!versionMajor.equalsIgnoreCase(v.getVersionMajor())) + { + return false; + } + if(!versionMinor.equalsIgnoreCase(v.getVersionMinor())) + { + return false; + } + if(!versionRevision.equalsIgnoreCase(v.getVersionRevision())) + { + return false; + } + } + return true; + } +} diff --git a/source/java/org/alfresco/repo/transfer/UnitTestInProcessTransmitterImpl.java b/source/java/org/alfresco/repo/transfer/UnitTestInProcessTransmitterImpl.java index 31adb1640c..6b63c809d9 100644 --- a/source/java/org/alfresco/repo/transfer/UnitTestInProcessTransmitterImpl.java +++ b/source/java/org/alfresco/repo/transfer/UnitTestInProcessTransmitterImpl.java @@ -34,6 +34,7 @@ import org.alfresco.service.cmr.transfer.TransferException; import org.alfresco.service.cmr.transfer.TransferProgress; import org.alfresco.service.cmr.transfer.TransferReceiver; import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.service.cmr.transfer.TransferVersion; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -63,14 +64,15 @@ public class UnitTestInProcessTransmitterImpl implements TransferTransmitter this.transactionService = transactionService; } - public Transfer begin(final TransferTarget target, final String fromRepositoryId) throws TransferException + public Transfer begin(final TransferTarget target, final String fromRepositoryId, final TransferVersion fromVersion) throws TransferException { return transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() { public Transfer execute() throws Throwable { Transfer transfer = new Transfer(); - String transferId = receiver.start(fromRepositoryId, true); + String transferId = receiver.start(fromRepositoryId, true, fromVersion); + transfer.setToVersion(receiver.getVersion()); transfer.setTransferId(transferId); transfer.setTransferTarget(target); return transfer; diff --git a/source/java/org/alfresco/repo/transfer/manifest/ManifestModel.java b/source/java/org/alfresco/repo/transfer/manifest/ManifestModel.java index 08f1a62217..3a1c01dca1 100644 --- a/source/java/org/alfresco/repo/transfer/manifest/ManifestModel.java +++ b/source/java/org/alfresco/repo/transfer/manifest/ManifestModel.java @@ -31,6 +31,7 @@ public interface ManifestModel extends TransferModel static final String LOCALNAME_HEADER_NODE_COUNT = "nodeCount"; static final String LOCALNAME_HEADER_SYNC = "sync"; static final String LOCALNAME_HEADER_RONLY = "readOnly"; + static final String LOCALNAME_HEADER_VERSION = "version"; static final String LOCALNAME_HEADER_REPOSITORY_ID = "repositoryId"; static final String LOCALNAME_ELEMENT_NODES = "nodes"; static final String LOCALNAME_ELEMENT_NODE = "node"; diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestHeader.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestHeader.java index b02c90e131..9739d867ea 100644 --- a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestHeader.java +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestHeader.java @@ -20,6 +20,8 @@ package org.alfresco.repo.transfer.manifest; import java.util.Date; +import org.alfresco.service.cmr.transfer.TransferVersion; + /** * Data value object * @@ -33,6 +35,7 @@ public class TransferManifestHeader private String repositoryId; private boolean isSync; private boolean isReadOnly; + private TransferVersion version; public void setCreatedDate(Date createDate) { @@ -97,6 +100,14 @@ public class TransferManifestHeader { return isReadOnly; } - + public void setTransferVersion(TransferVersion version) + { + this.version = version; + } + + public TransferVersion getTransferVersion() + { + return version; + } } diff --git a/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestReader.java b/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestReader.java index b3317d1f97..fbe20b6b24 100644 --- a/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestReader.java +++ b/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestReader.java @@ -33,6 +33,7 @@ import java.util.Map; import java.util.Map.Entry; import org.alfresco.repo.transfer.PathHelper; +import org.alfresco.repo.transfer.TransferVersionImpl; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; @@ -42,6 +43,7 @@ import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.cmr.transfer.TransferVersion; import org.alfresco.service.namespace.NamespaceException; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.QName; @@ -323,6 +325,16 @@ public class XMLTransferManifestReader extends DefaultHandler implements Content } props.put("acl", acl); } + else if(elementName.equals(ManifestModel.LOCALNAME_HEADER_VERSION)) + { + String versionMajor = (String)atts.getValue("", "versionMajor"); + String versionMinor = (String)atts.getValue("", "versionMinor"); + String versionRevision = (String)atts.getValue("", "versionRevision"); + String edition = (String)atts.getValue("", "edition"); + + props.put("headerVersion", new TransferVersionImpl(versionMajor, versionMinor, versionRevision, edition)); + } + else if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_ACL_PERMISSION)) { @@ -602,6 +614,12 @@ public class XMLTransferManifestReader extends DefaultHandler implements Content ContentData data = (ContentData)props.get("contentHeader"); props.put("value", data); } + else if(elementName.equals(ManifestModel.LOCALNAME_HEADER_VERSION)) + { + TransferManifestHeader header = (TransferManifestHeader)props.get("header"); + TransferVersion version = (TransferVersion)props.get("headerVersion"); + header.setTransferVersion(version); + } else if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_ACL)) { TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); diff --git a/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestWriter.java b/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestWriter.java index 562a008154..f9978d2999 100644 --- a/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestWriter.java +++ b/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestWriter.java @@ -40,6 +40,7 @@ import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.cmr.transfer.TransferVersion; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.dom4j.io.OutputFormat; @@ -179,6 +180,25 @@ public class XMLTransferManifestWriter implements TransferManifestWriter ManifestModel.LOCALNAME_HEADER_RONLY, PREFIX + ":" + ManifestModel.LOCALNAME_HEADER_RONLY); } + + TransferVersion version = header.getTransferVersion(); + if(version != null) + { + + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute("uri", "versionMajor", "versionMajor", "String", version.getVersionMajor()); + attributes.addAttribute("uri", "versionMinor", "versionMinor", "String", version.getVersionMinor()); + attributes.addAttribute("uri", "versionRevision", "versionRevision", "String", version.getVersionRevision()); + attributes.addAttribute("uri", "edition", "edition", "String", version.getEdition()); + + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, + ManifestModel.LOCALNAME_HEADER_VERSION, PREFIX + ":" + + ManifestModel.LOCALNAME_HEADER_VERSION, attributes); + + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, + ManifestModel.LOCALNAME_HEADER_VERSION, PREFIX + ":" + + ManifestModel.LOCALNAME_HEADER_VERSION); + } // End Header diff --git a/source/java/org/alfresco/repo/usage/RepoUsageComponent.java b/source/java/org/alfresco/repo/usage/RepoUsageComponent.java new file mode 100644 index 0000000000..a53663a840 --- /dev/null +++ b/source/java/org/alfresco/repo/usage/RepoUsageComponent.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.usage; + +import org.alfresco.service.cmr.admin.RepoUsage; +import org.alfresco.service.cmr.admin.RepoUsageStatus; +import org.alfresco.service.cmr.admin.RepoUsage.UsageType; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * Low-level interface to answer repository usage queries + * + * @author Derek Hulley + * @since 3.5 + */ +public interface RepoUsageComponent +{ + public static final Long LOCK_TTL = 60000L; + public static final QName LOCK_USAGE = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "RepoUsageComponent"); + public static final QName LOCK_USAGE_USERS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "RepoUsageComponent.Users"); + public static final QName LOCK_USAGE_DOCUMENTS = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "RepoUsageComponent.Documents"); + + public static final String KEY_USAGE_ROOT = ".repoUsages"; + public static final String KEY_USAGE_CURRENT = "current"; + public static final String KEY_USAGE_LAST_UPDATE_USERS = "lastUpdateUsers"; + public static final String KEY_USAGE_USERS = "users"; + public static final String KEY_USAGE_LAST_UPDATE_DOCUMENTS = "lastUpdateDocuments"; + public static final String KEY_USAGE_DOCUMENTS = "documents"; + + /** + * Observe when restrictions change + */ + void observeRestrictions(RestrictionObserver observer); + + /** + * Interface for observers of repository restriction changes + */ + public interface RestrictionObserver + { + /** + * Called when restrictions are changed + * + * @param restrictions the new restrictions + */ + void onChangeRestriction(RepoUsage restrictions); + }; + + /** + * Record changes to the restrictions imposed on the repository. These may be cached + * for fast access. This method should only be called if the {@link #getRestrictions() current restrictions} + * have changed. + * + * @param restrictions the new restrictions imposed on the repository + */ + void setRestrictions(RepoUsage restrictions); + + /** + * @return Returns the restrictions currently in play for the repository + */ + RepoUsage getRestrictions(); + + /** + * Force an update of the current repository usage. Usage data will be gathered and + * stored as required. + * + * @param usageType the type of usage data that must be updated + * @return true if the update succeeded or false if + * some other client was already performing the same update + */ + boolean updateUsage(UsageType usageType); + + /** + * Get the current repository usage data. This will not trigger an update of the data if it + * is not available; only {@link #updateUsage() pre-loaded data} will be used. + * + * @return Returns the repository-specific current usage data. + */ + RepoUsage getUsage(); + + /** + * Calculate and retrieve full status alerts based on the usage and license expiry state. + * + * @return Returns the usage status bean + */ + RepoUsageStatus getUsageStatus(); +} diff --git a/source/java/org/alfresco/repo/usage/RepoUsageComponentImpl.java b/source/java/org/alfresco/repo/usage/RepoUsageComponentImpl.java new file mode 100644 index 0000000000..efa47e6ea2 --- /dev/null +++ b/source/java/org/alfresco/repo/usage/RepoUsageComponentImpl.java @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.usage; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.ibatis.IdsEntity; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.domain.query.CannedQueryDAO; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.LockAcquisitionException; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.admin.RepoUsage; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; +import org.alfresco.service.cmr.admin.RepoUsage.UsageType; +import org.alfresco.service.cmr.admin.RepoUsageStatus; +import org.alfresco.service.cmr.admin.RepoUsageStatus.RepoUsageLevel; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.namespace.QName; +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.extensions.surf.util.I18NUtil; + +/** + * Low-level implementation to answer repository usage queries + * + * @author Derek Hulley + * @since 3.5 + */ +public class RepoUsageComponentImpl implements RepoUsageComponent +{ + private static final String QUERY_NS = "alfresco.query.usages"; + private static final String QUERY_SELECT_COUNT_PERSONS_NOT_DISABLED = "select_CountPersonsNotDisabled"; + private static final String QUERY_SELECT_COUNT_DOCUMENTS = "select_CountDocuments"; + + private static Log logger = LogFactory.getLog(RepoUsageComponentImpl.class); + + private TransactionService transactionService; + private AuthorityService authorityService; + private AttributeService attributeService; + private DictionaryService dictionaryService; + private JobLockService jobLockService; + private CannedQueryDAO cannedQueryDAO; + private QNameDAO qnameDAO; + + private RepoUsage restrictions; + private ReadLock restrictionsReadLock; + private WriteLock restrictionsWriteLock; + private Set restrictionObservers = new HashSet(); + + /** + * Defaults + */ + public RepoUsageComponentImpl() + { + this.restrictions = new RepoUsage(null, null, null, LicenseMode.UNKNOWN, null, false); + + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + restrictionsReadLock = lock.readLock(); + restrictionsWriteLock = lock.writeLock(); + } + + /** + * @param transactionService service that tells if the server is read-only or not + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * @param authorityService service to check for admin rights + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + /** + * @param attributeService service used to store usage attributes + */ + public void setAttributeService(AttributeService attributeService) + { + this.attributeService = attributeService; + } + + /** + * @param dictionaryService component to resolve types and subtypes + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param jobLockService service to prevent duplicate work when updating usages + */ + public void setJobLockService(JobLockService jobLockService) + { + this.jobLockService = jobLockService; + } + + /** + * @param cannedQueryDAO DAO for executing queries + */ + public void setCannedQueryDAO(CannedQueryDAO cannedQueryDAO) + { + this.cannedQueryDAO = cannedQueryDAO; + } + + /** + * @param qnameDAO DAO for getting IDs of QNames + */ + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + @Override + public void observeRestrictions(RestrictionObserver observer) + { + restrictionObservers.add(observer); + } + + /** + * Check that all properties are properly set + */ + public void init() + { + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "authorityService", authorityService); + PropertyCheck.mandatory(this, "attributeService", attributeService); + PropertyCheck.mandatory(this, "dictionaryService", dictionaryService); + PropertyCheck.mandatory(this, "jobLockService", jobLockService); + PropertyCheck.mandatory(this, "cannedQueryDAO", cannedQueryDAO); + PropertyCheck.mandatory(this, "qnameDAO", qnameDAO); + } + + /** + * Checks that the 'System' user is active in a read-write txn. + */ + private final void checkTxnState(TxnReadState txnStateNeeded) + { + switch (txnStateNeeded) + { + case TXN_READ_WRITE: + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE) + { + throw AlfrescoRuntimeException.create("system.usage.err.no_txn_readwrite"); + } + break; + case TXN_READ_ONLY: + if (AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_NONE) + { + throw AlfrescoRuntimeException.create("system.usage.err.no_txn"); + } + break; + } + } + + @Override + public void setRestrictions(RepoUsage restrictions) + { + checkTxnState(TxnReadState.TXN_NONE); + restrictionsWriteLock.lock(); + try + { + this.restrictions = restrictions; + } + finally + { + restrictionsWriteLock.unlock(); + } + + // Fire observers + for(RestrictionObserver observer : restrictionObservers ) + { + observer.onChangeRestriction(restrictions); + } + } + + @Override + public RepoUsage getRestrictions() + { + // No need to check txn state and any user can get this info. + restrictionsReadLock.lock(); + try + { + return restrictions; + } + finally + { + restrictionsReadLock.unlock(); + } + } + + @Override + public boolean updateUsage(UsageType usageType) + { + checkTxnState(TxnReadState.TXN_READ_WRITE); + + boolean updateUsers = false; + boolean updateDocuments = false; + switch (usageType) + { + case USAGE_DOCUMENTS: + updateDocuments = true; + break; + case USAGE_USERS: + updateUsers = true; + break; + case USAGE_ALL: + updateUsers = true; + updateDocuments = true; + } + + if (updateUsers && !updateUsers()) + { + return false; + } + if (updateDocuments && !updateDocuments()) + { + return false; + } + + // Done + if (logger.isDebugEnabled()) + { + RepoUsage usage = getUsageImpl(); + logger.debug("Updated repo usage: " + usage); + } + // The update succeeded and the locks held + return true; + } + + /** + * Update number of users with appropriate locking + */ + private boolean updateUsers() + { + String lockToken = null; + try + { + // Lock to prevent concurrent queries + lockToken = jobLockService.getLock(LOCK_USAGE_USERS, LOCK_TTL); + // Count users + IdsEntity idsParam = new IdsEntity(); + idsParam.setIdOne(qnameDAO.getOrCreateQName(ContentModel.ASPECT_PERSON_DISABLED).getFirst()); + idsParam.setIdTwo(qnameDAO.getOrCreateQName(ContentModel.TYPE_PERSON).getFirst()); + Long userCount = cannedQueryDAO.executeCountQuery(QUERY_NS, QUERY_SELECT_COUNT_PERSONS_NOT_DISABLED, idsParam); + + // We subtract one to cater for 'guest', which is implicit + userCount = userCount > 0L ? userCount - 1L : 0L; + + // Lock again to be sure we still have the right to update + jobLockService.refreshLock(lockToken, LOCK_USAGE_USERS, LOCK_TTL); + attributeService.setAttribute( + new Long(System.currentTimeMillis()), + KEY_USAGE_ROOT, KEY_USAGE_CURRENT, KEY_USAGE_LAST_UPDATE_USERS); + attributeService.setAttribute( + userCount, + KEY_USAGE_ROOT, KEY_USAGE_CURRENT, KEY_USAGE_USERS); + // Success + return true; + } + catch (LockAcquisitionException e) + { + logger.debug("Failed to get lock for user counts: " + e.getMessage()); + return false; + } + finally + { + if (lockToken != null) + { + jobLockService.releaseLock(lockToken, LOCK_USAGE_USERS); + } + } + } + + /** + * Update number of documents with appropriate locking + */ + private boolean updateDocuments() + { + String lockToken = null; + try + { + // Lock to prevent concurrent queries + lockToken = jobLockService.getLock(LOCK_USAGE_DOCUMENTS, LOCK_TTL); + // Count documents + Set searchTypeQNames = new HashSet(11); + Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true); + searchTypeQNames.addAll(qnames); + searchTypeQNames.add(ContentModel.TYPE_CONTENT); + qnames = dictionaryService.getSubTypes(ContentModel.TYPE_LINK, true); + searchTypeQNames.addAll(qnames); + searchTypeQNames.add(ContentModel.TYPE_LINK); + Set searchTypeQNameIds = qnameDAO.convertQNamesToIds(searchTypeQNames, false); + IdsEntity idsParam = new IdsEntity(); + idsParam.setIds(new ArrayList(searchTypeQNameIds)); + Long documentCount = cannedQueryDAO.executeCountQuery(QUERY_NS, QUERY_SELECT_COUNT_DOCUMENTS, idsParam); + + // Lock again to be sure we still have the right to update + jobLockService.refreshLock(lockToken, LOCK_USAGE_DOCUMENTS, LOCK_TTL); + attributeService.setAttribute( + new Long(System.currentTimeMillis()), + KEY_USAGE_ROOT, KEY_USAGE_CURRENT, KEY_USAGE_LAST_UPDATE_DOCUMENTS); + attributeService.setAttribute( + documentCount, + KEY_USAGE_ROOT, KEY_USAGE_CURRENT, KEY_USAGE_DOCUMENTS); + // Success + return true; + } + catch (LockAcquisitionException e) + { + logger.debug("Failed to get lock for document counts: " + e.getMessage()); + return false; + } + finally + { + if (lockToken != null) + { + jobLockService.releaseLock(lockToken, LOCK_USAGE_DOCUMENTS); + } + } + } + + /** + * Build the usage component. Protect with a read lock, transaction check and authentication check. + */ + private RepoUsage getUsageImpl() + { + // Fetch persisted usage data + Long lastUpdateUsers = (Long) attributeService.getAttribute( + KEY_USAGE_ROOT, KEY_USAGE_CURRENT, KEY_USAGE_LAST_UPDATE_USERS); + Long users = (Long) attributeService.getAttribute( + KEY_USAGE_ROOT, KEY_USAGE_CURRENT, KEY_USAGE_USERS); + Long lastUpdateDocuments = (Long) attributeService.getAttribute( + KEY_USAGE_ROOT, KEY_USAGE_CURRENT, KEY_USAGE_LAST_UPDATE_DOCUMENTS); + Long documents = (Long) attributeService.getAttribute( + KEY_USAGE_ROOT, KEY_USAGE_CURRENT, KEY_USAGE_DOCUMENTS); + + final Long lastUpdate; + if (lastUpdateUsers == null) + { + lastUpdate = lastUpdateDocuments; + } + else if (lastUpdateDocuments == null) + { + lastUpdate = lastUpdateUsers; + } + else if (lastUpdateDocuments.compareTo(lastUpdateUsers) > 0) + { + lastUpdate = lastUpdateDocuments; + } + else + { + lastUpdate = lastUpdateUsers; + } + + // Combine with current restrictions + RepoUsage usage = new RepoUsage( + lastUpdate, + users, + documents, + restrictions.getLicenseMode(), + restrictions.getLicenseExpiryDate(), + transactionService.getAllowWrite() == false); + return usage; + } + + @Override + public RepoUsage getUsage() + { + checkTxnState(TxnReadState.TXN_READ_ONLY); + restrictionsReadLock.lock(); + try + { + // Combine with current restrictions + RepoUsage usage = getUsageImpl(); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Retrieved repo usage: " + usage); + } + return usage; + } + finally + { + restrictionsReadLock.unlock(); + } + } + + /** + * Calculate and retrieve full status alerts based on the usage and license expiry state. + * + * @return Returns the usage status bean + */ + public RepoUsageStatus getUsageStatus() + { + RepoUsage usage = getUsage(); + RepoUsage restrictions = getRestrictions(); + + RepoUsageLevel level = RepoUsageLevel.OK; + List warnings = new ArrayList(1); + List errors = new ArrayList(1); + + // Check users + long usersCurrent = usage.getUsers() == null ? 0L : usage.getUsers(); + long usersMax = restrictions.getUsers() == null ? Long.MAX_VALUE : restrictions.getUsers(); + if (usersCurrent > usersMax) + { + errors.add(I18NUtil.getMessage("system.usage.err.limit_users_exceeded", usersMax, usersCurrent)); + level = RepoUsageLevel.LOCKED_DOWN; + } + else if (usersCurrent == usersMax) + { + warnings.add(I18NUtil.getMessage("system.usage.warn.limit_users_reached", usersMax, usersCurrent)); + level = RepoUsageLevel.WARN_ALL; + } + else if (usersCurrent >= (0.9 * usersMax) || usersCurrent >= (usersMax - 1)) + { + warnings.add(I18NUtil.getMessage("system.usage.warn.limit_users_approached", usersMax, usersCurrent)); + level = RepoUsageLevel.WARN_ADMIN; + } + + // Check documents + long documentsCurrent = usage.getDocuments() == null ? 0L : usage.getDocuments(); + long documentsMax = restrictions.getDocuments() == null ? Long.MAX_VALUE : restrictions.getDocuments(); + if (documentsCurrent > documentsMax) + { + errors.add(I18NUtil.getMessage("system.usage.err.limit_documents_exceeded", documentsMax, documentsCurrent)); + level = RepoUsageLevel.LOCKED_DOWN; + } + else if (documentsCurrent > 0.99 * documentsMax) + { + warnings.add(I18NUtil.getMessage("system.usage.warn.limit_documents_reached", documentsMax, documentsCurrent)); + if (level.ordinal() < RepoUsageLevel.WARN_ALL.ordinal()) + { + level = RepoUsageLevel.WARN_ALL; + } + } + else if (documentsCurrent > 0.9 * documentsMax) + { + warnings.add(I18NUtil.getMessage("system.usage.warn.limit_documents_approached", documentsMax, documentsCurrent)); + if (level.ordinal() < RepoUsageLevel.WARN_ADMIN.ordinal()) + { + level = RepoUsageLevel.WARN_ADMIN; + } + } + + // Check the license expiry + Long licenseExpiryDate = restrictions.getLicenseExpiryDate(); + if (licenseExpiryDate != null) + { + long remainingMs = licenseExpiryDate - System.currentTimeMillis(); + double remainingDays = (double) remainingMs / (double)(24*3600000); + if (remainingDays <= 0.0) + { + errors.add(I18NUtil.getMessage("system.usage.err.limit_license_expired")); + level = RepoUsageLevel.LOCKED_DOWN; + } + else if (remainingDays <= 7.0) + { + warnings.add(I18NUtil.getMessage("system.usage.err.limit_license_expiring", (int)remainingDays)); + if (level.ordinal() < RepoUsageLevel.WARN_ADMIN.ordinal()) + { + level = RepoUsageLevel.WARN_ADMIN; + } + } + else if (remainingDays <= 30.0) + { + warnings.add(I18NUtil.getMessage("system.usage.err.limit_license_expiring", (int)remainingDays)); + if (level.ordinal() < RepoUsageLevel.WARN_ALL.ordinal()) + { + level = RepoUsageLevel.WARN_ALL; + } + } + } + + RepoUsageStatus status = new RepoUsageStatus(restrictions, usage, level, warnings, errors); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Usage status generated: " + status); + } + return status; + } +} diff --git a/source/java/org/alfresco/repo/usage/RepoUsageComponentTest.java b/source/java/org/alfresco/repo/usage/RepoUsageComponentTest.java new file mode 100644 index 0000000000..f8a0977a80 --- /dev/null +++ b/source/java/org/alfresco/repo/usage/RepoUsageComponentTest.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.usage; + +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.admin.RepoUsage; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; +import org.alfresco.service.cmr.admin.RepoUsage.UsageType; +import org.alfresco.service.cmr.admin.RepoUsageStatus; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationContext; + +/** + * Tests {@link RepoUsageComponent} + * + * @author Derek Hulley + * @since 3.5 + */ +public class RepoUsageComponentTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private static final Log logger = LogFactory.getLog(RepoUsageComponentTest.class); + + private TransactionService transactionService; + private RepoUsageComponent repoUsageComponent; + private JobLockService jobLockService; + private UserTransaction txn; + private RepoUsage restrictionsBefore; + + @Override + protected void setUp() throws Exception + { + transactionService = (TransactionService) ctx.getBean("transactionComponent"); + repoUsageComponent = (RepoUsageComponent) ctx.getBean("repoUsageComponent"); + jobLockService = (JobLockService) ctx.getBean("jobLockService"); + + AuthenticationUtil.setRunAsUserSystem(); + + txn = transactionService.getUserTransaction(); + txn.begin(); + + restrictionsBefore = repoUsageComponent.getRestrictions(); + } + + @Override + protected void tearDown() throws Exception + { + // Reset restrictions + try + { + repoUsageComponent.setRestrictions(restrictionsBefore); + } + catch (Throwable e) + { + e.printStackTrace(); + } + + AuthenticationUtil.clearCurrentSecurityContext(); + if (txn != null) + { + try { txn.commit(); } catch (Throwable e) {} + } + } + + public void testSetup() + { + } + + /** + * Helper to wrap in a txn + */ + private RepoUsage getUsage() + { + RetryingTransactionCallback getCallback = new RetryingTransactionCallback() + { + @Override + public RepoUsage execute() throws Throwable + { + return repoUsageComponent.getUsage(); + } + }; + return transactionService.getRetryingTransactionHelper().doInTransaction(getCallback, true); + } + + /** + * Helper to wrap in a txn + */ + private boolean updateUsage(final UsageType usageType) + { + RetryingTransactionCallback getCallback = new RetryingTransactionCallback() + { + @Override + public Boolean execute() throws Throwable + { + return repoUsageComponent.updateUsage(usageType); + } + }; + return transactionService.getRetryingTransactionHelper().doInTransaction(getCallback, false); + } + + public void testNoTxn() throws Throwable + { + txn.commit(); + try + { + repoUsageComponent.getUsage(); + fail("Txn required for calls to RepoAdminComponent."); + } + catch (RuntimeException e) + { + // Expected + } + } + + public void testGetUsage() + { + getUsage(); + } + + public void testFullUse() throws Exception + { + // Set the restrictions + RepoUsage restrictions = new RepoUsage( + System.currentTimeMillis(), + 7L, + 600L, + LicenseMode.TEAM, + System.currentTimeMillis() + 24*3600000, + false); + repoUsageComponent.setRestrictions(restrictions); + // Get the restrictions (should not need a txn for this) + RepoUsage restrictionsCheck = repoUsageComponent.getRestrictions(); + assertEquals("Restrictions should return without change.", restrictions, restrictionsCheck); + + // Update use + updateUsage(UsageType.USAGE_ALL); + + // Get the usage + RepoUsage usage = getUsage(); + // Check + assertNotNull("Usage is null", usage); + assertNotNull("Invalid user count", usage.getUsers()); + assertNotNull("Invalid document count", usage.getDocuments()); + assertEquals("License mode not set", restrictions.getLicenseMode(), usage.getLicenseMode()); + assertEquals("License expiry not set", restrictions.getLicenseExpiryDate(), usage.getLicenseExpiryDate()); + assertEquals("Read-only state not set", restrictions.isReadOnly(), usage.isReadOnly()); + + RepoUsageStatus status = repoUsageComponent.getUsageStatus(); + logger.debug(status); + } + + /** + * Tests license code interaction. This interaction would be done using runAs 'System'. + */ + public void testLicenseUse() throws Exception + { + Long licenseUserLimit = 5L; + Long licenseDocumentLimit = 100000L; + LicenseMode licenseMode = LicenseMode.TEAM; + Long licenseExpiry = System.currentTimeMillis() + 24*3600000; + + // Get actual license details (incl. generating trial license) + // Push license restrictions + RepoUsage restrictions = new RepoUsage( + System.currentTimeMillis(), + licenseUserLimit, // From license + licenseDocumentLimit, // From license + licenseMode, // From license + licenseExpiry, // From license + transactionService.getAllowWrite() == false);// After license validity has been verified + repoUsageComponent.setRestrictions(restrictions); + // Trigger a usage update + updateUsage(UsageType.USAGE_ALL); + // Get the usage + @SuppressWarnings("unused") + RepoUsage usage = getUsage(); + } + + /** + * Check that concurrent updates are prevented + */ + public void testConcurrentUpdates() throws Exception + { + // Firstly check that we can get an update + assertTrue("Failed to update all usages", updateUsage(UsageType.USAGE_ALL)); + assertTrue("Failed to update user count", updateUsage(UsageType.USAGE_USERS)); + assertTrue("Failed to update document count", updateUsage(UsageType.USAGE_DOCUMENTS)); + + // Now take a lock of it all and see that they fail + String lockToken = jobLockService.getLock(RepoUsageComponent.LOCK_USAGE, RepoUsageComponent.LOCK_TTL); + try + { + // Check + assertFalse("Expected usage updates to be kicked out", updateUsage(UsageType.USAGE_ALL)); + assertFalse("Expected usage updates to be kicked out", updateUsage(UsageType.USAGE_USERS)); + assertFalse("Expected usage updates to be kicked out", updateUsage(UsageType.USAGE_DOCUMENTS)); + } + finally + { + jobLockService.releaseLock(lockToken, RepoUsageComponent.LOCK_USAGE); + } + + // Lock documents updates only + lockToken = jobLockService.getLock(RepoUsageComponent.LOCK_USAGE_DOCUMENTS, RepoUsageComponent.LOCK_TTL); + try + { + // Check + assertFalse("Expected usage updates to be kicked out", updateUsage(UsageType.USAGE_ALL)); + assertTrue("Failed to update user count", updateUsage(UsageType.USAGE_USERS)); + assertFalse("Expected document usage updates to be kicked out", updateUsage(UsageType.USAGE_DOCUMENTS)); + } + finally + { + jobLockService.releaseLock(lockToken, RepoUsageComponent.LOCK_USAGE_DOCUMENTS); + } + + // Lock user updates only + lockToken = jobLockService.getLock(RepoUsageComponent.LOCK_USAGE_USERS, RepoUsageComponent.LOCK_TTL); + try + { + // Check + assertFalse("Expected usage updates to be kicked out", updateUsage(UsageType.USAGE_ALL)); + assertFalse("Expected user usage updates to be kicked out", updateUsage(UsageType.USAGE_USERS)); + assertTrue("Failed to update document count", updateUsage(UsageType.USAGE_DOCUMENTS)); + } + finally + { + jobLockService.releaseLock(lockToken, RepoUsageComponent.LOCK_USAGE_USERS); + } + } +} diff --git a/source/java/org/alfresco/repo/usage/RepoUsageMonitor.java b/source/java/org/alfresco/repo/usage/RepoUsageMonitor.java new file mode 100644 index 0000000000..2a7e8f481f --- /dev/null +++ b/source/java/org/alfresco/repo/usage/RepoUsageMonitor.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.usage; + +import java.util.Date; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transaction.TransactionServiceImpl; +import org.alfresco.service.cmr.admin.RepoUsage; +import org.alfresco.service.cmr.admin.RepoUsage.UsageType; +import org.alfresco.service.cmr.admin.RepoUsageStatus; +import org.alfresco.service.cmr.admin.RepoUsageStatus.RepoUsageLevel; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +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.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.TriggerUtils; + +/** + * This component monitors the repository usages, issuing warnings and errors + * as necessary. + * + * @author Derek Hulley + * @since 3.5 + */ +public class RepoUsageMonitor implements RepoUsageComponent.RestrictionObserver +{ + private static Log logger = LogFactory.getLog(RepoUsageMonitor.class); + + private Scheduler scheduler; + private TransactionServiceImpl transactionService; + private RepoUsageComponent repoUsageComponent; + private final QName vetoName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "RepoUsageMonitor"); + + /** + * @param scheduler Timed updates + */ + public void setScheduler(Scheduler scheduler) + { + this.scheduler = scheduler; + } + + /** + * @param transactionService service that tells if the server is read-only or not + */ + public void setTransactionService(TransactionService transactionService) + { + try + { + this.transactionService = (TransactionServiceImpl) transactionService; + } + catch (ClassCastException e) + { + throw new AlfrescoRuntimeException("The RepoUsageMonitor needs direct access to the TransactionServiceImpl"); + } + } + + /** + * @param repoUsageComponent provides data on usages + */ + public void setRepoUsageComponent(RepoUsageComponent repoUsageComponent) + { + this.repoUsageComponent = repoUsageComponent; + } + + /** + * Check that all properties are properly set + */ + public void init() throws SchedulerException + { + PropertyCheck.mandatory(this, "scheduler", scheduler); + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "repoUsageComponent", repoUsageComponent); + + // Trigger the scheduled updates + final JobDetail jobDetail = new JobDetail("rmj", Scheduler.DEFAULT_GROUP, RepoUsageMonitorJob.class); + jobDetail.getJobDataMap().put("RepoUsageMonitor", this); + final Trigger trigger = TriggerUtils.makeHourlyTrigger(12); // every 12 hours + trigger.setStartTime(new Date(System.currentTimeMillis() + 60L * 60L * 1000L)); // one hour from now + trigger.setName("rmt"); + trigger.setGroup(Scheduler.DEFAULT_GROUP); + + repoUsageComponent.observeRestrictions(this); + + // Unschedule in case it was scheduled in an earlier retry of the transaction + scheduler.unscheduleJob("rmt", Scheduler.DEFAULT_GROUP); + scheduler.scheduleJob(jobDetail, trigger); + } + + /** + * Performs the physical checking of usages. + */ + public void checkUsages() + { + final RetryingTransactionCallback checkWork = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + RepoUsage restrictions = repoUsageComponent.getRestrictions(); + // Bypass if there are no restrictions + if (restrictions.getUsers() == null && restrictions.getDocuments() == null) + { + transactionService.setAllowWrite(true, vetoName); + return null; + } + + // Update user count, if required + if (restrictions.getUsers() != null) + { + repoUsageComponent.updateUsage(UsageType.USAGE_USERS); + } + // Update document count, if required + if (restrictions.getDocuments() != null) + { + repoUsageComponent.updateUsage(UsageType.USAGE_DOCUMENTS); + } + + // Same as if restrictions have been changed + onChangeRestriction(restrictions); + + return null; + } + }; + RunAsWork runAs = new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + transactionService.getRetryingTransactionHelper().doInTransaction(checkWork, false); + return null; + } + }; + AuthenticationUtil.runAs(runAs, AuthenticationUtil.getSystemUserName()); + } + + /** + * Checks the current status, logs messages and sets a read-write veto, if necessary + */ + @Override + public void onChangeRestriction(RepoUsage restrictions) + { + RepoUsageStatus status = repoUsageComponent.getUsageStatus(); + if (logger.isDebugEnabled()) + { + logger.debug("Current status is " + status); + } + + status.logMessages(logger); + + if (status.getLevel() == RepoUsageLevel.LOCKED_DOWN) + { + transactionService.setAllowWrite(false, vetoName); + } + else + { + transactionService.setAllowWrite(true, vetoName); + } + } + + /** + * The job that kicks off the usage monitoring. + * + * @author Derek Hulley + * @since V3.4 Team + */ + public static class RepoUsageMonitorJob implements Job + { + public void execute(final JobExecutionContext context) throws JobExecutionException + { + final JobDataMap jdm = context.getJobDetail().getJobDataMap(); + final RepoUsageMonitor repoUsageMonitor = (RepoUsageMonitor) jdm.get("RepoUsageMonitor"); + repoUsageMonitor.checkUsages(); + } + } +} diff --git a/source/java/org/alfresco/repo/usage/UsageTestSuite.java b/source/java/org/alfresco/repo/usage/UsageTestSuite.java new file mode 100644 index 0000000000..490a19a511 --- /dev/null +++ b/source/java/org/alfresco/repo/usage/UsageTestSuite.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.usage; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @author Derek Hulley + * @since V3.4 Team + */ +public class UsageTestSuite extends TestSuite +{ + public static ApplicationContext getContext() + { + ApplicationContextHelper.setUseLazyLoading(false); + ApplicationContextHelper.setNoAutoStart(true); + return ApplicationContextHelper.getApplicationContext( + new String[] { "classpath:alfresco/minimal-context.xml" } + ); + } + + public static Test suite() + { + // Setup the context + getContext(); + + TestSuite suite = new TestSuite(); + suite.addTestSuite(RepoUsageComponentTest.class); + suite.addTestSuite(UserUsageTrackingComponentTest.class); + return suite; + } +} diff --git a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponentTest.java b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponentTest.java index 25456c6ac3..a6a8b0fc17 100644 --- a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponentTest.java +++ b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponentTest.java @@ -43,6 +43,8 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; /** @@ -51,6 +53,8 @@ import org.springframework.context.ApplicationContext; public class UserUsageTrackingComponentTest extends TestCase { private static ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); + + private static Log logger = LogFactory.getLog(UserUsageTrackingComponentTest.class); private boolean clean = true; @@ -133,6 +137,7 @@ public class UserUsageTrackingComponentTest extends TestCase personService.createPerson(props); authenticationService.createAuthentication(userName, userName.toCharArray()); + authenticationService.setAuthenticationEnabled(userName, false); NodeRef homeFolder = getHomeSpaceFolderNode(userName); @@ -156,17 +161,17 @@ public class UserUsageTrackingComponentTest extends TestCase testTX.commit(); count = 0; - //System.out.println("Batch "+batch+" took "+((System.currentTimeMillis()-batchStart)/1000)+" secs"); + //logger.debug("Batch "+batch+" took "+((System.currentTimeMillis()-batchStart)/1000)+" secs"); } if (((i % PROGRESS_SIZE) == 0) && (i != MAX_USERS)) { - System.out.println("Progress: "+PROGRESS_SIZE+" users created in "+((System.currentTimeMillis()-progressStart)/1000)+" secs"); + logger.debug("Progress: "+PROGRESS_SIZE+" users created in "+((System.currentTimeMillis()-progressStart)/1000)+" secs"); progressStart = System.currentTimeMillis(); } } - System.out.println("Total: "+MAX_USERS+" users created in "+((System.currentTimeMillis()-start)/1000)+" secs"); + logger.debug("Total: "+MAX_USERS+" users created in "+((System.currentTimeMillis()-start)/1000)+" secs"); } catch (Throwable t) { @@ -177,18 +182,20 @@ public class UserUsageTrackingComponentTest extends TestCase } public void testEnableDisableCollapse() - { + { + logger.debug("Test: " + getName()); + userUsageTrackingComponent.setEnabled(false); userUsageTrackingComponent.bootstrapInternal(); // false => clear - System.out.println("Cleared usages"); + logger.debug("Cleared usages"); checkCleared(); userUsageTrackingComponent.setEnabled(true); userUsageTrackingComponent.bootstrapInternal(); // true => recalculate - System.out.println("Recalculated usages"); + logger.debug("Recalculated usages"); checkCalculated(2L); @@ -216,13 +223,13 @@ public class UserUsageTrackingComponentTest extends TestCase AuthenticationUtil.setRunAsUserSystem(); } - System.out.println("Added content"); + logger.debug("Added content"); checkUsage(4L); userUsageTrackingComponent.execute(); // collapse usages - System.out.println("Collapsed usages"); + logger.debug("Collapsed usages"); checkUsage(4L); @@ -244,20 +251,20 @@ public class UserUsageTrackingComponentTest extends TestCase AuthenticationUtil.setRunAsUserSystem(); } - System.out.println("Deleted content"); + logger.debug("Deleted content"); checkUsage(2L); userUsageTrackingComponent.execute(); // collapse usages - System.out.println("Collapsed usages"); + logger.debug("Collapsed usages"); checkUsage(2L); userUsageTrackingComponent.setEnabled(false); userUsageTrackingComponent.bootstrapInternal(); // false => clear - System.out.println("Cleared usages"); + logger.debug("Cleared usages"); checkCleared(); } @@ -338,11 +345,11 @@ public class UserUsageTrackingComponentTest extends TestCase testTX.commit(); count = 0; - //System.out.println("Batch "+batch+" took "+((System.currentTimeMillis()-batchStart)/1000)+" secs"); + //logger.debug("Batch "+batch+" took "+((System.currentTimeMillis()-batchStart)/1000)+" secs"); } } - System.out.println("Total: "+deleteCount+" users deleted in "+((System.currentTimeMillis()-start)/1000)+" secs"); + logger.debug("Total: "+deleteCount+" users deleted in "+((System.currentTimeMillis()-start)/1000)+" secs"); } catch (Throwable t) { diff --git a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java index c4c97a91e9..2d8b6782a2 100644 --- a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java +++ b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java @@ -204,9 +204,20 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe versionProperties.putAll(origVersionProperties); } + // We don't want either the adding of the Versionable aspect, or the setting + // of the version label, to affect the auditable properties on the node that + // is being versioned. + // So, disable the auditable aspect on that node for now + boolean disableAuditable = policyBehaviourFilter.isEnabled(ContentModel.ASPECT_AUDITABLE); + if(disableAuditable) + { + policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + } + // If the version aspect is not there then add it to the 'live' (versioned) node if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) { + // Add the versionable aspect to the node this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, null); } @@ -352,6 +363,12 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe ContentModel.PROP_VERSION_LABEL, version.getVersionLabel()); + // Re-enable the auditable aspect (if we turned it off earlier) + if(disableAuditable) + { + policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + } + // Invoke the policy behaviour invokeAfterCreateVersion(nodeRef, version); diff --git a/source/java/org/alfresco/repo/version/VersionServiceImpl.java b/source/java/org/alfresco/repo/version/VersionServiceImpl.java index d23c634231..fd3d30859c 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImpl.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImpl.java @@ -37,7 +37,7 @@ import org.alfresco.repo.version.common.AbstractVersionServiceImpl; import org.alfresco.repo.version.common.VersionHistoryImpl; import org.alfresco.repo.version.common.VersionImpl; import org.alfresco.repo.version.common.VersionUtil; -import org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicy; +import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -882,6 +882,57 @@ public class VersionServiceImpl extends AbstractVersionServiceImpl implements Ve return result; } + + /** + * @see org.alfresco.cms.version.VersionService#ensureVersioningEnabled(NodeRef,Map) + */ + public void ensureVersioningEnabled(NodeRef nodeRef, Map versionProperties) + { + // Don't alter the auditable aspect! + boolean disableAuditable = policyBehaviourFilter.isEnabled(ContentModel.ASPECT_AUDITABLE); + if(disableAuditable) + { + policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + } + + // Do we need to apply the aspect? + if (! nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + // Only apply new properties that are version ones + AspectDefinition versionable = + dictionaryService.getAspect(ContentModel.ASPECT_VERSIONABLE); + Set versionAspectProperties = + versionable.getProperties().keySet(); + + Map props = new HashMap(); + if(versionProperties != null &&! versionProperties.isEmpty()) + { + for(QName prop : versionProperties.keySet()) + { + if(versionAspectProperties.contains(prop)) + { + // This property is one from the versionable aspect + props.put(prop, versionProperties.get(prop)); + } + } + } + + // Add the aspect + nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props); + } + + // Do we need to create the initial version history entry? + if(getVersionHistory(nodeRef) == null) + { + createVersion(nodeRef, null); + } + + // Put Auditable back + if(disableAuditable) + { + policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + } + } /** * @see org.alfresco.cms.version.VersionService#revert(NodeRef) diff --git a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java index 26675f6570..2df349ae2f 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java @@ -33,6 +33,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.model.WebDAVModel; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.service.ServiceRegistry; @@ -974,6 +975,22 @@ public class VersionServiceImplTest extends BaseVersionStoreTest }); + //Checking whether VersionModel.PROP_VERSION_TYPE set to MINOR type after update node properties + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + nodeService.setProperty(versionableNode, ContentModel.PROP_DESCRIPTION, "test description"); + VersionHistory versionHistory = versionService.getVersionHistory(versionableNode); + VersionType vType = (VersionType) versionHistory.getHeadVersion().getVersionProperty(VersionModel.PROP_VERSION_TYPE); + assertNotNull("Is not setted the version type", vType); + assertEquals(vType, VersionType.MINOR); + return null; + } + + }); + + // test auto-version props off startNewTransaction(); @@ -1086,6 +1103,20 @@ public class VersionServiceImplTest extends BaseVersionStoreTest }); + //Checking whether VersionModel.PROP_VERSION_TYPE set to MINOR type after update node properties + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + VersionHistory versionHistory = versionService.getVersionHistory(versionableNode); + VersionType vType = (VersionType) versionHistory.getHeadVersion().getVersionProperty(VersionModel.PROP_VERSION_TYPE); + assertNotNull("Is not setted the version type", vType); + assertEquals(vType, VersionType.MINOR); + return null; + } + + }); + // test auto-version props on - with a non-excluded prop change transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() @@ -1321,6 +1352,159 @@ public class VersionServiceImplTest extends BaseVersionStoreTest assertEquals(oldVersion.getVersionProperty(nodeUuidKey), newVersion.getVersionProperty(nodeUuidKey)); } } + + /** + * Ensure that versioning actions don't alter the auditable + * aspect properties on the original nodes + */ + public void testVersioningAndAuditable() throws Exception { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + if(!authenticationDAO.userExists(USER_NAME_A)) + { + authenticationService.createAuthentication(USER_NAME_A, PWD_A.toCharArray()); + } + + // Create a node as the "A" user + NodeRef nodeA = AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public NodeRef doWork() throws Exception + { + return transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + AuthenticationUtil.setFullyAuthenticatedUser(USER_NAME_A); + NodeRef a = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}NodeForA"), + ContentModel.TYPE_CONTENT + ).getChildRef(); + nodeService.addAspect(a, ContentModel.ASPECT_AUDITABLE, null); + return a; + } + } + ); + } + }, USER_NAME_A + ); + + // Check that it's owned by A + assertEquals(USER_NAME_A, nodeService.getProperty(nodeA, ContentModel.PROP_CREATOR)); + assertEquals(USER_NAME_A, nodeService.getProperty(nodeA, ContentModel.PROP_MODIFIER)); + assertEquals(false, nodeService.hasAspect(nodeA, ContentModel.ASPECT_VERSIONABLE)); + + // Now enable it for versioning, as Admin + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + versionService.createVersion(nodeA, null); + + // Ensure it's still owned by A + assertEquals(USER_NAME_A, nodeService.getProperty(nodeA, ContentModel.PROP_CREATOR)); + assertEquals(USER_NAME_A, nodeService.getProperty(nodeA, ContentModel.PROP_MODIFIER)); + assertEquals(true, nodeService.hasAspect(nodeA, ContentModel.ASPECT_VERSIONABLE)); + } + + public void testEnsureVersioningEnabled() throws Exception + { + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + if(!authenticationDAO.userExists(USER_NAME_A)) + { + authenticationService.createAuthentication(USER_NAME_A, PWD_A.toCharArray()); + } + + // Create 3 nodes in the 3 different states + AuthenticationUtil.setFullyAuthenticatedUser(USER_NAME_A); + NodeRef none = nodeService.createNode( + rootNodeRef, ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}None"), + ContentModel.TYPE_CONTENT + ).getChildRef(); + nodeService.addAspect(none, ContentModel.ASPECT_AUDITABLE, null); + + NodeRef aspect = nodeService.createNode( + rootNodeRef, ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}None"), + ContentModel.TYPE_CONTENT + ).getChildRef(); + nodeService.addAspect(aspect, ContentModel.ASPECT_AUDITABLE, null); + nodeService.addAspect(aspect, ContentModel.ASPECT_VERSIONABLE, null); + nodeService.setProperty(aspect, ContentModel.PROP_AUTO_VERSION, Boolean.FALSE); + nodeService.setProperty(aspect, ContentModel.PROP_AUTO_VERSION_PROPS, Boolean.TRUE); + + NodeRef versioned = nodeService.createNode( + rootNodeRef, ContentModel.ASSOC_CONTAINS, + QName.createQName("{test}None"), + ContentModel.TYPE_CONTENT + ).getChildRef(); + nodeService.addAspect(versioned, ContentModel.ASPECT_AUDITABLE, null); + nodeService.addAspect(versioned, ContentModel.ASPECT_VERSIONABLE, null); + nodeService.setProperty(versioned, ContentModel.PROP_AUTO_VERSION, Boolean.TRUE); + nodeService.setProperty(versioned, ContentModel.PROP_AUTO_VERSION_PROPS, Boolean.FALSE); + versionService.createVersion(versioned, null); + + + // Check their state + assertEquals(false, nodeService.hasAspect(none, ContentModel.ASPECT_VERSIONABLE)); + assertEquals(true, nodeService.hasAspect(aspect, ContentModel.ASPECT_VERSIONABLE)); + assertEquals(true, nodeService.hasAspect(versioned, ContentModel.ASPECT_VERSIONABLE)); + assertNull(versionService.getVersionHistory(none)); + assertNull(versionService.getVersionHistory(aspect)); + assertNotNull(versionService.getVersionHistory(versioned)); + + assertEquals(USER_NAME_A, nodeService.getProperty(none, ContentModel.PROP_CREATOR)); + assertEquals(USER_NAME_A, nodeService.getProperty(none, ContentModel.PROP_MODIFIER)); + assertEquals(USER_NAME_A, nodeService.getProperty(aspect, ContentModel.PROP_CREATOR)); + assertEquals(USER_NAME_A, nodeService.getProperty(aspect, ContentModel.PROP_MODIFIER)); + assertEquals(USER_NAME_A, nodeService.getProperty(versioned, ContentModel.PROP_CREATOR)); + assertEquals(USER_NAME_A, nodeService.getProperty(versioned, ContentModel.PROP_MODIFIER)); + + + // If we turn on the aspect, what with? + Map props = new HashMap(); + props.put(ContentModel.PROP_TITLE, "This shouldn't be set by the method"); + props.put(ContentModel.PROP_AUTO_VERSION, Boolean.TRUE); + + + // Now call ensureVersioningEnabled for each + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + versionService.ensureVersioningEnabled(none, props); + versionService.ensureVersioningEnabled(aspect, props); + versionService.ensureVersioningEnabled(versioned, props); + + // And finally check their state: + + // None will have the aspect applied, along with both properties + assertEquals(true, nodeService.hasAspect(none, ContentModel.ASPECT_VERSIONABLE)); + assertEquals(Boolean.TRUE, nodeService.getProperty(none, ContentModel.PROP_AUTO_VERSION)); + assertEquals(Boolean.TRUE, nodeService.getProperty(none, ContentModel.PROP_AUTO_VERSION_PROPS)); + assertEquals(null, nodeService.getProperty(none, ContentModel.PROP_TITLE)); + + // Aspect won't have altered it's props + assertEquals(true, nodeService.hasAspect(aspect, ContentModel.ASPECT_VERSIONABLE)); + assertEquals(Boolean.FALSE, nodeService.getProperty(aspect, ContentModel.PROP_AUTO_VERSION)); + assertEquals(Boolean.TRUE, nodeService.getProperty(aspect, ContentModel.PROP_AUTO_VERSION_PROPS)); + assertEquals(null, nodeService.getProperty(aspect, ContentModel.PROP_TITLE)); + + // Versioned won't have altered it's props + assertEquals(true, nodeService.hasAspect(versioned, ContentModel.ASPECT_VERSIONABLE)); + assertEquals(Boolean.TRUE, nodeService.getProperty(versioned, ContentModel.PROP_AUTO_VERSION)); + assertEquals(Boolean.FALSE, nodeService.getProperty(versioned, ContentModel.PROP_AUTO_VERSION_PROPS)); + assertEquals(null, nodeService.getProperty(versioned, ContentModel.PROP_TITLE)); + + // Alll will have a version history now + assertNotNull(versionService.getVersionHistory(none)); + assertNotNull(versionService.getVersionHistory(aspect)); + assertNotNull(versionService.getVersionHistory(versioned)); + + // The auditable properties won't have changed + assertEquals(USER_NAME_A, nodeService.getProperty(none, ContentModel.PROP_CREATOR)); + assertEquals(USER_NAME_A, nodeService.getProperty(none, ContentModel.PROP_MODIFIER)); + assertEquals(USER_NAME_A, nodeService.getProperty(aspect, ContentModel.PROP_CREATOR)); + assertEquals(USER_NAME_A, nodeService.getProperty(aspect, ContentModel.PROP_MODIFIER)); + assertEquals(USER_NAME_A, nodeService.getProperty(versioned, ContentModel.PROP_CREATOR)); + assertEquals(USER_NAME_A, nodeService.getProperty(versioned, ContentModel.PROP_MODIFIER)); + } public static void main(String ... args) { diff --git a/source/java/org/alfresco/repo/version/VersionServicePolicies.java b/source/java/org/alfresco/repo/version/VersionServicePolicies.java index 8aea8bf304..3b08247734 100644 --- a/source/java/org/alfresco/repo/version/VersionServicePolicies.java +++ b/source/java/org/alfresco/repo/version/VersionServicePolicies.java @@ -25,6 +25,7 @@ import org.alfresco.repo.policy.ClassPolicy; import org.alfresco.repo.policy.PolicyScope; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; /** @@ -39,6 +40,7 @@ public interface VersionServicePolicies */ public interface BeforeCreateVersionPolicy extends ClassPolicy { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "beforeCreateVersion"); /** * Called before a new version is created for a version * @@ -54,6 +56,7 @@ public interface VersionServicePolicies */ public interface AfterCreateVersionPolicy extends ClassPolicy { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "afterCreateVersion"); /** * Called after the version has been created * @@ -68,6 +71,7 @@ public interface VersionServicePolicies */ public interface OnCreateVersionPolicy extends ClassPolicy { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateVersion"); /** * Called during the creation of the version to determine what the versioning policy for a * perticular type may be. diff --git a/source/java/org/alfresco/repo/version/VersionableAspect.java b/source/java/org/alfresco/repo/version/VersionableAspect.java index 3aecd64d9a..bdcb5ea092 100644 --- a/source/java/org/alfresco/repo/version/VersionableAspect.java +++ b/source/java/org/alfresco/repo/version/VersionableAspect.java @@ -40,6 +40,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.cmr.version.VersionType; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; @@ -400,8 +401,9 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate } // Create the auto-version - Map versionProperties = new HashMap(1); + Map versionProperties = new HashMap(4); versionProperties.put(Version.PROP_DESCRIPTION, I18NUtil.getMessage(MSG_AUTO_VERSION_PROPS)); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MINOR); createVersionImpl(nodeRef, versionProperties); } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java index c004a10d51..1dfa90196a 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/AlfrescoTimer.java @@ -85,10 +85,12 @@ public class AlfrescoTimer extends Timer { boolean deleteTimer = AlfrescoTimer.super.execute(jbpmContext); - // End the task. + // End the task if timer does not repeat. // Note the order is a little odd here as the task will be ended // after the token has been signalled to move to the next node. - if (taskInstance != null && taskInstance.isOpen()) + if (deleteTimer + && taskInstance != null + && taskInstance.isOpen()) { taskInstance.setSignalling(false); taskInstance.end(); diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index 9580b9dbcd..4690f3cdda 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -23,7 +23,6 @@ import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; @@ -31,6 +30,9 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import java.util.zip.ZipInputStream; import org.alfresco.model.ContentModel; @@ -70,7 +72,9 @@ import org.alfresco.service.namespace.NamespaceException; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; +import org.hibernate.CacheMode; import org.hibernate.Criteria; +import org.hibernate.FlushMode; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.criterion.Conjunction; @@ -130,6 +134,8 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine protected StoreRef companyHomeStore; protected String companyHomePath; + private QNameCache qNameCache = new QNameCache(); + // Note: jBPM query which is not provided out-of-the-box // TODO: Check jBPM 3.2 and get this implemented in jBPM private final static String COMPLETED_TASKS_QUERY = "select ti " + "from org.jbpm.taskmgmt.exe.TaskInstance as ti " @@ -790,37 +796,16 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine return new WorkflowException(msg, e); } - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getActiveWorkflows(java. - * lang.String) - */ public List getActiveWorkflows(final String workflowDefinitionId) { return getWorkflowsInternal(workflowDefinitionId, true); } - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getCompletedWorkflows(java - * .lang.String) - */ public List getCompletedWorkflows(final String workflowDefinitionId) { return getWorkflowsInternal(workflowDefinitionId, false); } - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.WorkflowComponent#getWorkflows(java.lang.String - * ) - */ public List getWorkflows(final String workflowDefinitionId) { return getWorkflowsInternal(workflowDefinitionId, null); @@ -1315,6 +1300,11 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { try { + // arsenyko: I think it's necessary to avoid undesirable growth, + // since a cache implementation is quite simple and doesn't care about + // max elements in the cache. + qNameCache.clear(); + return (List) jbpmTemplate.execute(new JbpmCallback() { public List doInJbpm(JbpmContext context) @@ -1323,16 +1313,22 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine List tasks; if (state.equals(WorkflowTaskState.IN_PROGRESS)) { - TaskMgmtSession taskSession = context.getTaskMgmtSession(); - tasks = taskSession.findTaskInstances(authority); + Session session = context.getSession(); + Query query = session.getNamedQuery("org.alfresco.repo.workflow.findTaskInstancesByActorId"); + query.setString("actorId", authority); + query.setBoolean("true", true); + List workflowTasks = getWorkflowTasks(session, query.list()); + // Do we need to clear a session here? It takes 3 seconds with 2000 workflows. + // session.clear(); + return workflowTasks; } else { // Note: This method is not implemented by jBPM tasks = findCompletedTaskInstances(context, authority); + return getWorkflowTasks(tasks); } - return getWorkflowTasks(tasks); } /** @@ -1347,6 +1343,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine * the actor to retrieve tasks for * @return the tasks */ + @SuppressWarnings("rawtypes") private List findCompletedTaskInstances(JbpmContext jbpmContext, String actorId) { List result = null; @@ -1373,12 +1370,6 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine } } - /* - * (non-Javadoc) - * - * @see - * org.alfresco.repo.workflow.TaskComponent#getPooledTasks(java.util.List) - */ @SuppressWarnings("unchecked") public List getPooledTasks(final List authorities) { @@ -1438,6 +1429,218 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine } } + protected List getWorkflowTasks(Session session, List rows) + { + List workflowTasks = new ArrayList(rows.size()); + + /// ------------------------ + + // Preload data into L1 session + List taskInstanceIds = new ArrayList(rows.size()); + List contextInstanceIds = new ArrayList(rows.size()); + for (Object[] row : rows) + { + TaskInstance ti = (TaskInstance) row[0]; + taskInstanceIds.add(ti.getId()); + ContextInstance ci = (ContextInstance) row[8]; + contextInstanceIds.add(ci.getId()); + } + Map taskInstanceCache = new HashMap(rows.size()); + if (taskInstanceIds.size() > 0) + { + taskInstanceCache = cacheTasks(session, taskInstanceIds); + } + Map variablesCache = new HashMap(rows.size()); + if (contextInstanceIds.size() > 0) + { + variablesCache = cacheVariables(session, contextInstanceIds); + } + taskInstanceIds.clear(); + contextInstanceIds.clear(); + /// ------------------------ + for(Object[] row : rows) + { + TaskInstance ti = (TaskInstance) row[0]; + Token token = (Token)row[2]; + ProcessInstance processInstance = (ProcessInstance)row[3]; + Node node = (Node)row[4]; + Task task = (Task)row[5]; + ProcessDefinition processDefinition = (ProcessDefinition)row[6]; + Task startTask = (Task)row[7]; + ContextInstance contextInstance = (ContextInstance) row[8]; + + if (tenantService.isEnabled()) + { + try + { + tenantService.checkDomain(processDefinition.getName()); + } + catch (RuntimeException re) + { + // deliberately skip this one - due to domain mismatch - eg. when querying by group authority + continue; + } + } + // TaskInstance with some precached properties + TaskInstance helperTi = taskInstanceCache.get(ti.getId()); + // WorkflowTask + WorkflowTask workflowTask = new WorkflowTask(); + workflowTask.id = createGlobalId(new Long(ti.getId()).toString()); + workflowTask.name = ti.getName(); + workflowTask.state = getWorkflowTaskState(ti); + // WorkflowPath + WorkflowPath path = new WorkflowPath(); + String tokenId = token.getFullName().replace("/", WORKFLOW_TOKEN_SEPERATOR); + path.id = createGlobalId(processInstance.getId() + WORKFLOW_PATH_SEPERATOR + tokenId); + // WorkflowInstance + WorkflowInstance workflowInstance = new WorkflowInstance(); + workflowInstance.id = createGlobalId(new Long(processInstance.getId()).toString()); + + @SuppressWarnings("unchecked") + Map variables = variablesCache.get(contextInstance.getId()).getVariables(); + + workflowInstance.description = (String)variables.get(mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION)); + String name = tenantService.getBaseName(processDefinition.getName()); + String title = getLabel(name + ".workflow", TITLE_LABEL, name); + String description = getLabel(name + ".workflow", DESC_LABEL, title); + workflowInstance.definition = new WorkflowDefinition(createGlobalId(new Long(processDefinition.getId()).toString()), + createGlobalId(name), + new Integer(processDefinition.getVersion()).toString(), + title, + description, + (startTask != null + ? createWorkflowTaskDefinition(startTask) + : null)); + workflowInstance.active = !processInstance.hasEnded(); + JBPMNode initiator = (JBPMNode)variables.get("initiator"); + if (initiator != null) + { + workflowInstance.initiator = initiator.getNodeRef(); + } + JBPMNode context = (JBPMNode)variables.get(mapQNameToName(WorkflowModel.PROP_CONTEXT)); + if (context != null) + { + workflowInstance.context = context.getNodeRef(); + } + JBPMNode workflowPackage = (JBPMNode)variables.get(mapQNameToName(WorkflowModel.ASSOC_PACKAGE)); + if (workflowPackage != null) + { + workflowInstance.workflowPackage = workflowPackage.getNodeRef(); + } + workflowInstance.priority = (Integer)variables.get(mapQNameToName(WorkflowModel.PROP_WORKFLOW_PRIORITY)); + workflowInstance.dueDate = (Date)variables.get(mapQNameToName(WorkflowModel.PROP_WORKFLOW_DUE_DATE)); + workflowInstance.startDate = processInstance.getStart(); + workflowInstance.endDate = processInstance.getEnd(); + path.instance = workflowInstance; + // WorkflowNode + path.node = createWorkflowNode(node); + path.active = !token.hasEnded(); + workflowTask.path = path; + // WorkflowTaskDefinition + workflowTask.definition = createWorkflowTaskDefinition(task); + // WorkflowTaskProperies + workflowTask.properties = getTaskProperties(helperTi != null ? helperTi : ti, false, variablesCache); + + String workflowDisplayId = processDefinition.getName() + ".task." + workflowTask.name; + workflowTask.title = getLabel(workflowDisplayId, TITLE_LABEL, null); + if (workflowTask.title == null) + { + workflowTask.title = workflowTask.definition.metadata.getTitle(); + if (workflowTask.title == null) + { + workflowTask.title = workflowTask.name; + } + } + workflowTask.description = getLabel(workflowDisplayId, DESC_LABEL, null); + if (workflowTask.description == null) + { + description = workflowTask.definition.metadata.getDescription(); + workflowTask.description = (description == null) ? workflowTask.title : description; + } + + workflowTasks.add(workflowTask); + } + return workflowTasks; + } + + private Map cacheVariables(Session session, List ids) + { + // Preload data into L1 session + int batchSize = 800; // Must limit IN clause size! + List batch = new ArrayList(ids.size()); + Map cachedResults = new HashMap(); + for (Long id : ids) + { + batch.add(id); + if (batch.size() >= batchSize) + { + cacheVariablesNoBatch(session, batch, cachedResults); + batch.clear(); + } + } + if (batch.size() > 0) + { + cacheVariablesNoBatch(session, batch, cachedResults); + } + batch.clear(); + return cachedResults; + } + + @SuppressWarnings("unchecked") + private void cacheVariablesNoBatch(Session session, List contextInstanceIds, Map variablesCache) + { + Query query = session.getNamedQuery("org.alfresco.repo.workflow.cacheInstanceVariables"); + query.setParameterList("ids", contextInstanceIds); + query.setCacheMode(CacheMode.PUT); + query.setFlushMode(FlushMode.MANUAL); + query.setCacheable(true); + + List results = (List) query.list(); + for (TokenVariableMap tokenVariableMap : results) + { + variablesCache.put(tokenVariableMap.getContextInstance().getId(), tokenVariableMap); + } + } + + private Map cacheTasks(Session session, List ids) + { + // Preload data into L1 session + int batchSize = 800; // Must limit IN clause size! + List batch = new ArrayList(ids.size()); + Map cachedResults = new HashMap(); + for (Long id : ids) + { + batch.add(id); + if (batch.size() >= batchSize) + { + cacheTasksNoBatch(session, batch, cachedResults); + batch.clear(); + } + } + if (batch.size() > 0) + { + cacheTasksNoBatch(session, batch, cachedResults); + } + batch.clear(); + return cachedResults; + } + + @SuppressWarnings("unchecked") + private void cacheTasksNoBatch(Session session, List taskInstanceIds, Map returnMap) + { + Query query = session.getNamedQuery("org.alfresco.repo.workflow.cacheTaskInstanceProperties"); + query.setParameterList("ids", taskInstanceIds); + query.setCacheMode(CacheMode.PUT); + query.setFlushMode(FlushMode.MANUAL); + query.setCacheable(true); + + List results = (List) query.list(); + for (TaskInstance taskInstance : results) + { + returnMap.put(taskInstance.getId(), taskInstance); + } + } + protected List getWorkflowTasks(List tasks) { // convert tasks to appropriate service response format @@ -1558,13 +1761,11 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine // note: constrain process variables to same criteria as tasks createProcessCriteria(variables, query); - // retrieve list of processes matching specified variables - List processList = variables.list(); - Object[] processIds = getProcessIds( processList); + Disjunction processIdCriteria = createProcessIdCriteria(variables); // constrain tasks by process list process = (process == null) ? task.createCriteria("processInstance") : process; - process.add(Restrictions.in("id", processIds)); + process.add(processIdCriteria); } } @@ -1634,6 +1835,43 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine return task; } + /** + * @param variables + * @return + */ + private Disjunction createProcessIdCriteria(Criteria variables) + { + // retrieve list of processes matching specified variables + List processList = variables.list(); + Object[] processIds = getProcessIds( processList); + + // ALF-5841 fix + int batch = 0; + List buf = new ArrayList(1000); + + Disjunction ids = Restrictions.disjunction(); + for (Object id : processIds) + { + if (batch < 1000) + { + batch++; + buf.add(id); + } + else + { + ids.add(Restrictions.in("id", buf)); + batch = 0; + buf.clear(); + } + } + + if (!buf.isEmpty()) + { + ids.add(Restrictions.in("id", buf)); + } + return ids; + } + private Object[] getProcessIds(List processList) { ArrayList ids = new ArrayList(processList.size()); @@ -1769,7 +2007,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine { newProperties.putAll(properties); } - Map existingProperties = getTaskProperties(taskInstance, false); + Map existingProperties = getTaskProperties(taskInstance, false, null); if (add != null) { // add new associations @@ -2179,13 +2417,12 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine /** * Gets Properties of Task * - * @param instance - * task instance - * @param properties - * properties to set + * @param instance task instance + * @param properties properties to set + * @param variablesCache cahce og context instance variables if any exists */ @SuppressWarnings("unchecked") - protected Map getTaskProperties(TaskInstance instance, boolean localProperties) + protected Map getTaskProperties(TaskInstance instance, boolean localProperties, Map variablesCache) { // retrieve type definition for task TypeDefinition taskDef = getFullTaskDefinition(instance); @@ -2200,7 +2437,16 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine Token token = instance.getToken(); while (token != null) { - TokenVariableMap varMap = context.getTokenVariableMap(token); + TokenVariableMap varMap = null; + if (variablesCache != null && variablesCache.containsKey(context.getId())) + { + varMap = variablesCache.get(context.getId()); + } + else + { + varMap = context.getTokenVariableMap(token); + } + if (varMap != null) { Map tokenVars = varMap.getVariablesLocally(); @@ -2486,7 +2732,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine */ protected void setDefaultTaskProperties(TaskInstance instance) { - Map existingValues = getTaskProperties(instance, true); + Map existingValues = getTaskProperties(instance, true, null); Map defaultValues = new HashMap(); // construct an anonymous type that flattens all mandatory aspects @@ -2560,7 +2806,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine */ protected void setDefaultWorkflowProperties(TaskInstance startTask) { - Map taskProperties = getTaskProperties(startTask, true); + Map taskProperties = getTaskProperties(startTask, true, null); ContextInstance processContext = startTask.getContextInstance(); String workflowDescriptionName = mapQNameToName(WorkflowModel.PROP_WORKFLOW_DESCRIPTION); @@ -2606,7 +2852,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine List missingProps = null; // retrieve properties of task - Map existingValues = getTaskProperties(instance, false); + Map existingValues = getTaskProperties(instance, false, null); // retrieve definition of task ClassDefinition classDef = getFullTaskDefinition(instance); @@ -2821,7 +3067,9 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine */ private QName mapNameToQName(String name) { - QName qname = null; + QName qname = qNameCache.getNameToQName(name); + if (qname == null) + { // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as // new scheme (i.e. } -> _) String qnameStr = (name.indexOf('}') == -1) ? name.replaceFirst("_", ":") : name.replace("}", ":"); @@ -2832,6 +3080,8 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine catch(NamespaceException e) { qname = QName.createQName(name, this.namespaceService); + } + qNameCache.putNameToQName(name, qname); } return qname; } @@ -2848,12 +3098,18 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine // NOTE: Map names using old conversion scheme (i.e. : -> _) as well as // new scheme (i.e. } -> _) // NOTE: Use new scheme + String cachedName = qNameCache.getQNameToName(name); + if (cachedName == null) + { String nameStr = name.toPrefixString(this.namespaceService); if (nameStr.indexOf('_') != -1 && nameStr.indexOf('_') < nameStr.indexOf(':')) { - return nameStr.replace(':', '}'); + cachedName = nameStr.replace(':', '}'); + } + cachedName = nameStr.replace(':', '_'); } - return nameStr.replace(':', '_'); + qNameCache.putQNameToName(name, cachedName); + return cachedName; } /** @@ -2944,6 +3200,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine String type = getRealNode(node).getClass().getSimpleName(); // TODO: Is there a formal way of determing if task node? boolean isTaskNode = type.equals("TaskNode"); + @SuppressWarnings("rawtypes") List transitions = node.getLeavingTransitions(); List wfTransitions; if (transitions != null) @@ -3066,7 +3323,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine WorkflowPath path = createWorkflowPath(task.getToken()); WorkflowTaskState state = getWorkflowTaskState(task); WorkflowTaskDefinition definition = createWorkflowTaskDefinition(task.getTask()); - Map properties = getTaskProperties(task, false); + Map properties = getTaskProperties(task, false, null); return factory.createTask(id, definition, name, null, null, state, path, properties); } @@ -3163,4 +3420,103 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine } } + /** + * Basic cache implementation for performance improvement with mapping QNames and Names of properties. + */ + private class QNameCache + { + private Map mapQNameToNameCache; + private Map mapNameToQNameCache; + + private ReentrantReadWriteLock qNameToNameLock = new ReentrantReadWriteLock(); + private WriteLock qNameToNameWriteLock = qNameToNameLock.writeLock(); + private ReadLock qNameToNameReadLock = qNameToNameLock.readLock(); + + private ReentrantReadWriteLock nameToQNameLock = new ReentrantReadWriteLock(); + private WriteLock nameToQNameWriteLock = nameToQNameLock.writeLock(); + private ReadLock nameToQNameReadLock = nameToQNameLock.readLock(); + + QNameCache() + { + mapQNameToNameCache = new HashMap(); + mapNameToQNameCache = new HashMap(); + } + + String getQNameToName(QName qName) + { + qNameToNameReadLock.lock(); + try + { + return mapQNameToNameCache.get(qName); + } + finally + { + qNameToNameReadLock.unlock(); + } + } + + void putQNameToName(QName qName, String name) + { + qNameToNameWriteLock.lock(); + try + { + mapQNameToNameCache.put(qName, name); + } + finally + { + qNameToNameWriteLock.unlock(); + } + } + + QName getNameToQName(String name) + { + nameToQNameReadLock.lock(); + try + { + return mapNameToQNameCache.get(name); + } + finally + { + nameToQNameReadLock.unlock(); + } + } + + void putNameToQName(String name, QName qName) + { + nameToQNameWriteLock.lock(); + try + { + mapNameToQNameCache.put(name, qName); + } + finally + { + nameToQNameWriteLock.unlock(); + } + } + + void clear() + { + nameToQNameWriteLock.lock(); + try + { + mapNameToQNameCache.clear(); + } + finally + { + nameToQNameWriteLock.unlock(); + } + qNameToNameWriteLock.lock(); + try + { + mapQNameToNameCache.clear(); + } + finally + { + qNameToNameWriteLock.unlock(); + } + + } + + } + } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMJunit4LoadTests.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMJunit4LoadTests.java new file mode 100644 index 0000000000..a82ea6159d --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMJunit4LoadTests.java @@ -0,0 +1,239 @@ +package org.alfresco.repo.workflow.jbpm; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.namespace.QName; +import org.hibernate.Query; +import org.hibernate.Session; +import org.jbpm.JbpmContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springmodules.workflow.jbpm31.JbpmCallback; +import org.springmodules.workflow.jbpm31.JbpmTemplate; + +/** + * This test shows a performance benefit from a usage of direct queries + * instead of creating required classes like WorkflowTask in a loop with collecting + * required properties from different services. + * + * @author arsenyko + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "classpath:alfresco/application-context.xml" }) +public class JBPMJunit4LoadTests +{ + + private static String WORKFLOW_NAME = "jbpm$wf:adhoc"; + private static String WORKFLOW_NODE_NAME = "workflow-test-19243cbb-c58a-485e-bcd9-2e2be030dfb9.txt"; + + private static int WORKFLOW_COUNT = 2000; + + @Autowired + protected ApplicationContext applicationContext; + protected ServiceRegistry serviceRegistry; + protected RetryingTransactionHelper retryingTransactionHelper; + protected NodeService nodeService; + protected WorkflowService workflowService; + protected FileFolderService fileFolderService; + + protected JBPMEngine jbpmEngine; + + protected AuthenticationComponent authenticationComponent; + + private NodeRef companyHomeNodeRef; + + @Before + public void setUp() throws Exception + { + serviceRegistry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + serviceRegistry.getAuthenticationService().authenticate("admin", "admin".toCharArray()); + + retryingTransactionHelper = serviceRegistry.getRetryingTransactionHelper(); + fileFolderService = serviceRegistry.getFileFolderService(); + workflowService = serviceRegistry.getWorkflowService(); + nodeService = serviceRegistry.getNodeService(); + + BPMEngineRegistry registry = (BPMEngineRegistry)applicationContext.getBean("bpm_engineRegistry"); + jbpmEngine = (JBPMEngine) registry.getWorkflowComponent("jbpm"); + + NodeRef storeRootNodeRef = nodeService.getRootNode(new StoreRef("workspace://SpacesStore")); + companyHomeNodeRef = serviceRegistry.getSearchService().selectNodes(storeRootNodeRef, "/app:company_home", null, serviceRegistry.getNamespaceService(), false).get(0); + System.out.println(" -------------- "); + createWorkflowStuff(); + } + + public void createWorkflowStuff() throws Exception + { + System.out.println(" [createWorkflowStuff] Started at " + new Date().toString()); + + NodeRef workflowNode = nodeService.getChildByName(companyHomeNodeRef, ContentModel.ASSOC_CONTAINS, WORKFLOW_NODE_NAME); + + if (workflowNode == null) + { + RetryingTransactionCallback callback = new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + FileInfo fileInfo = fileFolderService.create(companyHomeNodeRef, WORKFLOW_NODE_NAME, ContentModel.TYPE_CONTENT); + ContentWriter writer = serviceRegistry.getContentService().getWriter(fileInfo.getNodeRef(), ContentModel.PROP_CONTENT, true); + writer.setMimetype("text/plain"); + writer.setEncoding("UTF-8"); + writer.putContent("many workflows many workflows many workflows many workflows many workflows many workflows many workflows many workflows"); + System.out.println(" [createWorkflowStuff] Workflow node '" + WORKFLOW_NODE_NAME + "' has been created"); + + //WorkflowDefinition wfDef = workflowService.getDefinitionByName(WORKFLOW_NAME); + WorkflowDefinition wfDef = jbpmEngine.getDefinitionByName(WORKFLOW_NAME); + long startTime = new Date().getTime(); + for (Integer i = 0; i < WORKFLOW_COUNT; i++) + { + // We are creating workflows in usual way, but with new persistent objects. + // There is a some performance issue with sesssion.flash() in each iteration, + // but this was made to avoid a lot of changes in a logic related to org.alfresco.service.cmr.workflow.* + // classes. + workflowService.startWorkflow(wfDef.id, prepareWorkflowProperties(fileInfo.getNodeRef(), i.toString())); + // jbpmEngine.startWorkflow_ALF1787(wfDef.id, prepareWorkflowProperties(fileInfo.getNodeRef(), i.toString())); + } + long endTime = new Date().getTime(); + System.out.println(" [createWorkflowStuff] Execution time (ms): " + (endTime - startTime)); + return null; + } + + }; + retryingTransactionHelper.setMaxRetries(1); + retryingTransactionHelper.doInTransaction(callback); + System.out.println(" [createWorkflowStuff] Finished at " + new Date().toString()); + } + else + { + System.out.println(" [createWorkflowStuff] Workflow node '" + WORKFLOW_NODE_NAME + "' already exists"); + } + } + + @SuppressWarnings("unchecked") + //@Test + public void testQuery1() throws Exception + { + RetryingTransactionCallback callback = new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + JbpmTemplate jbpmTemplate = (JbpmTemplate) applicationContext.getBean("jbpm_template"); + List result = (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + Session session = context.getSession(); + Query query = session.getNamedQuery("org.alfresco.repo.workflow.findTaskInstancesByActorId"); + return query.setString("actorId", "admin").list(); + } + }); + for(Object[] ti : result) + { + System.out.println(Arrays.toString(ti)); + } + System.out.println(result.size()); + return null; + } + }; + retryingTransactionHelper.setMaxRetries(1); + retryingTransactionHelper.doInTransaction(callback); + } + + @Test + public void testGetAssignedTasks_NEW() throws Exception + { + final int RUN_COUNT = 7; + RetryingTransactionCallback callback = new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + Date beginTime = new Date(); + System.out.println(" [testGetAssignedTasks_NEW] Started at " + beginTime.toString()); + List tasks = workflowService.getAssignedTasks("admin", WorkflowTaskState.IN_PROGRESS); + Date endTime = new Date(); + System.out.println(" [testGetAssignedTasks_NEW] Retrieved tasks: " + tasks.size() + " in " + (endTime.getTime() - beginTime.getTime()) + " ms"); + System.out.println(" [testGetAssignedTasks_NEW] Finished at " + endTime.toString()); + return null; + } + }; + retryingTransactionHelper.setMaxRetries(1); + for(int i=0; i callback = new RetryingTransactionCallback(){ + + @Override + public Void execute() throws Throwable + { + Date beginTime = new Date(); + System.out.println(" [testGetAssignedTasks_OLD] Started at " + beginTime.toString()); + List tasks = jbpmEngine.getAssignedTasks_OLD("admin", WorkflowTaskState.IN_PROGRESS); + Date endTime = new Date(); + System.out.println(" [testGetAssignedTasks_OLD] Retrieved tasks: " + tasks.size() + " in " + (endTime.getTime() - beginTime.getTime()) + " ms"); + System.out.println(" [testGetAssignedTasks_OLD] Finished at " + new Date().toString()); + return null; + } + }; + retryingTransactionHelper.setMaxRetries(1); + retryingTransactionHelper.doInTransaction(callback); + } + */ + + @After + public void tearDown() throws Exception + { + System.out.println(" -------------- "); + } + + private Map prepareWorkflowProperties(NodeRef nodeRef, String id) + { + Map parameters = new HashMap(); + + parameters.put(WorkflowModel.ASSOC_PACKAGE, nodeRef); + parameters.put(WorkflowModel.ASSOC_ASSIGNEE, "admin"); + parameters.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, "Test workflow '" + id + "'"); + parameters.put(WorkflowModel.PROP_WORKFLOW_DEFINITION_NAME, "test_workflow_" + id); + + return parameters; + + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/NodeListConverter.java b/source/java/org/alfresco/repo/workflow/jbpm/NodeListConverter.java index c0e16522e2..77a488f73c 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/NodeListConverter.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/NodeListConverter.java @@ -19,11 +19,13 @@ package org.alfresco.repo.workflow.jbpm; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; import org.jbpm.context.exe.converter.SerializableToByteArrayConverter; +import org.jbpm.graph.def.ProcessDefinition; import org.springframework.beans.factory.access.BeanFactoryLocator; import org.springframework.beans.factory.access.BeanFactoryReference; import org.springmodules.workflow.jbpm31.JbpmFactoryLocator; @@ -33,13 +35,13 @@ import org.springmodules.workflow.jbpm31.JbpmFactoryLocator; * jBPM Converter for transforming Alfresco Node to string and back * * @author davidc + * @author Nick Smith */ public class NodeListConverter extends SerializableToByteArrayConverter { private static final long serialVersionUID = 1L; private static BeanFactoryLocator jbpmFactoryLocator = new JbpmFactoryLocator(); - /** * {@inheritDoc} @@ -78,24 +80,49 @@ public class NodeListConverter extends SerializableToByteArrayConverter * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") public Object revert(Object o) { Object reverted = null; if (o != null) { - List nodeRefs = (List)super.revert(o); - BeanFactoryReference factory = jbpmFactoryLocator.useBeanFactory(null); - ServiceRegistry serviceRegistry = (ServiceRegistry)factory.getFactory().getBean(ServiceRegistry.SERVICE_REGISTRY); - - JBPMNodeList nodes = new JBPMNodeList(); - for (NodeRef nodeRef : nodeRefs) - { - nodes.add(new JBPMNode(nodeRef, serviceRegistry)); - } - reverted = nodes; + Object nodeRefs = super.revert(o); + reverted = revertNodes(nodeRefs); } return reverted; } + /** + * {@inheritDoc} + */ + @Override + public Object revert(Object o, ProcessDefinition processDefinition) + { + Object reverted = null; + if (o != null) + { + Object nodeRefs = super.revert(o, processDefinition); + reverted = revertNodes(nodeRefs); + } + return reverted; + } + + /** + * @param nodeRefs + * @return + */ + @SuppressWarnings("unchecked") + private JBPMNodeList revertNodes(Object value) + { + BeanFactoryReference factory = jbpmFactoryLocator.useBeanFactory(null); + ServiceRegistry serviceRegistry = (ServiceRegistry)factory.getFactory().getBean(ServiceRegistry.SERVICE_REGISTRY); + + JBPMNodeList nodes = new JBPMNodeList(); + Collection nodeRefs = (Collection) value; + for (NodeRef nodeRef : nodeRefs) + { + nodes.add(new JBPMNode(nodeRef, serviceRegistry)); + } + return nodes; + } + } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/jbpm.ext.queries.hbm.xml b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.ext.queries.hbm.xml new file mode 100644 index 0000000000..e091d89c38 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.ext.queries.hbm.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + :true + AND taskInstance.ISOPEN_= :true + ]]> + + + + select taskInstance + from org.jbpm.taskmgmt.exe.TaskInstance as taskInstance + left join fetch taskInstance.token as token + left join fetch taskInstance.task as task + left join fetch token.processInstance as processInstance + left join fetch processInstance.processDefinition as processDefinition + left join fetch processDefinition.definitions as taskMgmtDefinition + where taskInstance.actorId = :actorId + and taskInstance.isSuspended != true + and taskInstance.isOpen = true + + + + + + + + + + + diff --git a/source/java/org/alfresco/service/ServiceRegistry.java b/source/java/org/alfresco/service/ServiceRegistry.java index fa0f1b6d87..78f11c9566 100644 --- a/source/java/org/alfresco/service/ServiceRegistry.java +++ b/source/java/org/alfresco/service/ServiceRegistry.java @@ -24,11 +24,13 @@ import org.alfresco.cmis.CMISDictionaryService; import org.alfresco.cmis.CMISQueryService; import org.alfresco.cmis.CMISServices; import org.alfresco.mbeans.VirtServerRegistry; +import org.alfresco.repo.admin.SysAdminParams; import org.alfresco.repo.forms.FormService; import org.alfresco.repo.imap.ImapService; import org.alfresco.repo.lock.JobLockService; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.admin.RepoAdminService; import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.avm.AVMService; @@ -134,6 +136,7 @@ public interface ServiceRegistry static final QName PREFERENCE_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "PreferenceService"); static final QName RENDITION_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "RenditionService"); static final QName RATING_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "RatingService"); + static final QName REPO_ADMIN_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "RepoAdminService"); // WCM / AVM static final QName AVM_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "AVMService"); @@ -548,5 +551,17 @@ public interface ServiceRegistry @NotAuditable PublicServiceAccessService getPublicServiceAccessService(); - + /** + * Get the repo admin service (or null if one is not provided) + * @return the invitation service + */ + @NotAuditable + RepoAdminService getRepoAdminService(); + + /** + * Get the sys admin params helper bean. + * @return the sys admin params bean. + */ + @NotAuditable + SysAdminParams getSysAdminParams(); } diff --git a/source/java/org/alfresco/service/cmr/action/Action.java b/source/java/org/alfresco/service/cmr/action/Action.java index ec3d091a3a..8e12006058 100644 --- a/source/java/org/alfresco/service/cmr/action/Action.java +++ b/source/java/org/alfresco/service/cmr/action/Action.java @@ -41,168 +41,188 @@ public interface Action extends ParameterizedItem */ NodeRef getNodeRef(); - /** - * Get the name of the action definition that relates to this action - * - * @return the action defintion name - */ - String getActionDefinitionName(); - - /** - * Get the title of the action - * - * @return the title of the action - */ - String getTitle(); - - /** - * Set the title of the action - * - * @param title the title of the action - */ - void setTitle(String title); - - /** - * Get the description of the action - * - * @return the description of the action - */ - String getDescription(); - - /** - * Set the description of the action - * - * @param description the description of the action - */ - void setDescription(String description); - - /** - * Gets a value indicating whether the action should be executed asychronously or not. - *

    - * The default is to execute the action synchronously. - * - * @return true if the action is executed asychronously, false otherwise. - */ - boolean getExecuteAsychronously(); - - /** - * Set the value that indicates whether the action should be executed asychronously or not. - * - * @param executeAsynchronously true if the action is to be executed asychronously, false otherwise. - */ - void setExecuteAsynchronously(boolean executeAsynchronously); - - /** - * Get the compensating action. - *

    - * This action is executed if the failure behaviour is to compensate and the action being executed - * fails. - * - * @return the compensating action - */ - Action getCompensatingAction(); - - /** - * Set the compensating action. - * - * @param action the compensating action - */ - void setCompensatingAction(Action action); - - /** - * Get the date the action was created - * - * @return action creation date - */ - Date getCreatedDate(); - - /** - * Get the name of the user that created the action - * - * @return user name - */ - String getCreator(); - - /** - * Get the date that the action was last modified - * - * @return aciton modification date - */ - Date getModifiedDate(); - - /** - * Get the name of the user that last modified the action - * - * @return user name - */ - String getModifier(); - - /** - * Indicates whether the action has any conditions specified - * - * @return true if the action has any conditions specified, flase otherwise - */ - boolean hasActionConditions(); - - /** - * Gets the index of an action condition - * - * @param actionCondition the action condition - * @return the index - */ - int indexOfActionCondition(ActionCondition actionCondition); - - /** - * Gets a list of the action conditions for this action - * - * @return list of action conditions - */ - List getActionConditions(); - - /** - * Get the action condition at a given index - * - * @param index the index - * @return the action condition - */ - ActionCondition getActionCondition(int index); - - /** - * Add an action condition to the action - * - * @param actionCondition an action condition - */ - void addActionCondition(ActionCondition actionCondition); - - /** - * Add an action condition at the given index - * - * @param index the index - * @param actionCondition the action condition - */ - void addActionCondition(int index, ActionCondition actionCondition); - - /** - * Replaces the current action condition at the given index with the - * action condition provided. - * - * @param index the index - * @param actionCondition the action condition - */ - void setActionCondition(int index, ActionCondition actionCondition); - - /** - * Removes an action condition - * - * @param actionCondition an action condition - */ - void removeActionCondition(ActionCondition actionCondition); - - /** - * Removes all action conditions - */ - void removeAllActionConditions(); - + /** + * Get the name of the action definition that relates to this action + * + * @return the action defintion name + */ + String getActionDefinitionName(); + + /** + * Get the title of the action + * + * @return the title of the action + */ + String getTitle(); + + /** + * Set the title of the action + * + * @param title the title of the action + */ + void setTitle(String title); + + /** + * Get the description of the action + * + * @return the description of the action + */ + String getDescription(); + + /** + * Set the description of the action + * + * @param description the description of the action + */ + void setDescription(String description); + + /** + * @return true if the action must be tracked by the + * {@link ActionTrackingService}, false if it must NOT be + * tracked or null to use the action definition's default. + */ + Boolean getTrackStatus(); + + /** + * Set whether the action should be tracked or not. + *

    + * The option of tracking can be null i.e. unset, indicating that the + * defaults of the action definition should be used. This is to allow manual overriding + * of the property when it becomes supported by the UI. + * + * @param trackStatus true if the action must be tracked by the + * {@link ActionTrackingService}, false if it must NOT be + * tracked or null to use the action definition's default. + */ + void setTrackStatus(Boolean trackStatus); + + /** + * Gets a value indicating whether the action should be executed asychronously or not. + *

    + * The default is to execute the action synchronously. + * + * @return true if the action is executed asychronously, false otherwise. + */ + boolean getExecuteAsychronously(); + + /** + * Set the value that indicates whether the action should be executed asychronously or not. + * + * @param executeAsynchronously true if the action is to be executed asychronously, false otherwise. + */ + void setExecuteAsynchronously(boolean executeAsynchronously); + + /** + * Get the compensating action. + *

    + * This action is executed if the failure behaviour is to compensate and the action being executed + * fails. + * + * @return the compensating action + */ + Action getCompensatingAction(); + + /** + * Set the compensating action. + * + * @param action the compensating action + */ + void setCompensatingAction(Action action); + + /** + * Get the date the action was created + * + * @return action creation date + */ + Date getCreatedDate(); + + /** + * Get the name of the user that created the action + * + * @return user name + */ + String getCreator(); + + /** + * Get the date that the action was last modified + * + * @return aciton modification date + */ + Date getModifiedDate(); + + /** + * Get the name of the user that last modified the action + * + * @return user name + */ + String getModifier(); + + /** + * Indicates whether the action has any conditions specified + * + * @return true if the action has any conditions specified, flase otherwise + */ + boolean hasActionConditions(); + + /** + * Gets the index of an action condition + * + * @param actionCondition the action condition + * @return the index + */ + int indexOfActionCondition(ActionCondition actionCondition); + + /** + * Gets a list of the action conditions for this action + * + * @return list of action conditions + */ + List getActionConditions(); + + /** + * Get the action condition at a given index + * + * @param index the index + * @return the action condition + */ + ActionCondition getActionCondition(int index); + + /** + * Add an action condition to the action + * + * @param actionCondition an action condition + */ + void addActionCondition(ActionCondition actionCondition); + + /** + * Add an action condition at the given index + * + * @param index the index + * @param actionCondition the action condition + */ + void addActionCondition(int index, ActionCondition actionCondition); + + /** + * Replaces the current action condition at the given index with the + * action condition provided. + * + * @param index the index + * @param actionCondition the action condition + */ + void setActionCondition(int index, ActionCondition actionCondition); + + /** + * Removes an action condition + * + * @param actionCondition an action condition + */ + void removeActionCondition(ActionCondition actionCondition); + + /** + * Removes all action conditions + */ + void removeAllActionConditions(); + /** * Adds a {@link Map} of parameter values to the {@link Action} * @param values A map of values to be added diff --git a/source/java/org/alfresco/service/cmr/action/ActionDefinition.java b/source/java/org/alfresco/service/cmr/action/ActionDefinition.java index 0b9a47da46..3daa92060c 100644 --- a/source/java/org/alfresco/service/cmr/action/ActionDefinition.java +++ b/source/java/org/alfresco/service/cmr/action/ActionDefinition.java @@ -22,8 +22,6 @@ import java.util.List; import org.alfresco.service.namespace.QName; - - /** * Rule action interface. * @@ -37,4 +35,16 @@ public interface ActionDefinition extends ParameterizedItemDefinition * @return list of types */ public List getApplicableTypes(); + + /** + * Get whether the basic action definition supports action tracking + * or not. This can be overridden for each {@link Action#getTrackStatus() action} + * but if not, this value is used. Defaults to false. + * + * @return true to track action execution status or false (default) + * to do no action tracking + * + * @since 3.4.1 + */ + public boolean getTrackStatus(); } diff --git a/source/java/org/alfresco/service/cmr/activities/ActivityService.java b/source/java/org/alfresco/service/cmr/activities/ActivityService.java index 10e42ea828..cbc580d844 100644 --- a/source/java/org/alfresco/service/cmr/activities/ActivityService.java +++ b/source/java/org/alfresco/service/cmr/activities/ActivityService.java @@ -20,9 +20,9 @@ package org.alfresco.service.cmr.activities; import java.util.List; -import org.alfresco.service.PublicService; -import org.alfresco.service.Auditable; +import org.alfresco.repo.domain.activities.ActivityFeedEntity; import org.alfresco.service.NotAuditable; +import org.alfresco.service.PublicService; /** @@ -69,6 +69,28 @@ public interface ActivityService extends ActivityPostService @NotAuditable public List getUserFeedEntries(String userId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers); + /** + * Retrieve user feed with optional site filter and optional user filters and optional min feed DB id + * + * Will return activities for users across all sites, or optionally for users for specified site. + * + * User filters are: + * - all user activities (excludeThisUser = false, excludeOtherUsers = false) + * - other user activities (excludeThisUser = true, excludeOtherUsers = false) + * - my user activities (excludeThisUser = false, excludeOtherUsers = true) + * note: if both excludes are true then no activities will be returned. + * + * @param userId - required + * @param format - required + * @param siteId - optional, if set then will filter by given siteId else return all sites + * @param excludeThisUser - if TRUE then will exclude activities for this user (hence returning other users only) + * @param excludeOthersUsers - if TRUE then will exclude activities for other users (hence returning this user only) + * @param minFeedId - inclusive from min feed DB id, if -1 then return all available + * @return list of JSON feed entries + */ + @NotAuditable + public List getUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId); + /** * Retrieve site feed * @@ -80,6 +102,14 @@ public interface ActivityService extends ActivityPostService public List getSiteFeedEntries(String siteId, String format); + /** + * Return maximum configured item entries (per feed) + * + * @return + */ + @NotAuditable + public int getMaxFeedItems(); + /* * Manage User Feed Controls */ diff --git a/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java b/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java index bf45fadd8d..646989f7db 100644 --- a/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java +++ b/source/java/org/alfresco/service/cmr/admin/RepoAdminService.java @@ -24,6 +24,7 @@ import java.util.List; import org.alfresco.repo.admin.RepoModelDefinition; import org.alfresco.service.Auditable; import org.alfresco.service.PublicService; +import org.alfresco.service.cmr.admin.RepoUsage.UsageType; import org.alfresco.service.namespace.QName; @@ -103,4 +104,46 @@ public interface RepoAdminService @Auditable(parameters = {"bundleBaseName"}) public void reloadMessageBundle(String bundleBaseName); + // + // Usage + // + + /** + * Get the currently-active restrictions to the repository usage + * + * @since 3.5 + */ + @Auditable + public RepoUsage getRestrictions(); + + /** + * Get the repository usage, where known + * + * @return the currently-known repository usage + * + * @since 3.5 + */ + public RepoUsage getUsage(); + + /** + * Force an update of the usages, providing a hint on the specific updates required. + * If another client is already performing the update, then the calling code will need + * to determine the severity i.e. is an updated value really needed. Generally + * clients should accept that the data might be slightly stale, especially since there + * is no way to guarantee visibility of data being put into the database by other + * transactions. + * + * @param usageType the type of usage update to perform + * @return true if the update succeeded or false if + * some other client was already performing the same update + */ + public boolean updateUsage(UsageType usageType); + + /** + * Get full information on the state of the usage limits, including errors and warnings + * about limits in play. + * + * @return the object containing all the information + */ + public RepoUsageStatus getUsageStatus(); } diff --git a/source/java/org/alfresco/service/cmr/admin/RepoUsage.java b/source/java/org/alfresco/service/cmr/admin/RepoUsage.java new file mode 100644 index 0000000000..5a16ced891 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/admin/RepoUsage.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.service.cmr.admin; + +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.ParameterCheck; + +/** + * Bean holding the known or unknown usage values of the repository. + * + * @author Derek Hulley + * @since 3.4 + */ +public class RepoUsage +{ + /* + * DH: + * This class could operate using a Map to store limits and restrictions dynamically. + * Policies could be wired in to do the comparisons. For expedience and simplicity, + * the supported limits and associated behaviour are hard-coded. + */ + + /** + * Enumeration of the common usage types + * + * @author Derek Hulley + * @since 3.4 + */ + public enum UsageType + { + /** + * Identifies usage: user count + */ + USAGE_USERS, + /** + * Identifies usage: document count + */ + USAGE_DOCUMENTS, + /** + * Identifies usage: all types of usage + */ + USAGE_ALL + } + + /** + * Enumeration of the server license modes. + * + * @author Derek Hulley + * @since 3.4 + */ + public enum LicenseMode + { + /** + * The server is running with full Enteprise license. + */ + ENTERPRISE, + /** + * The server is running with a Team license. + */ + TEAM, + /** + * The license mode is unknown. + */ + UNKNOWN + } + + private final Long lastUpdate; + private final Long users; + private final Long documents; + private final LicenseMode licenseMode; + private final Long licenseExpiryDate; + private final boolean readOnly; + + /** + * @param lastUpdate the time the repository usage was last updated + * @param users the number of users or null if not known + * @param documents the number of documents or null if not known + * @param licenseMode the server license mode in effect at runtime + * @param licenseExpiryDate the date that the license expires or null if it doesn't + * @param readOnly true if the server is currently read-only + */ + public RepoUsage( + Long lastUpdate, + Long users, + Long documents, + LicenseMode licenseMode, + Long licenseExpiryDate, + boolean readOnly) + { + ParameterCheck.mandatory("licenseMode", licenseMode); + + this.lastUpdate = lastUpdate; + this.users = users; + this.documents = documents; + this.licenseMode = licenseMode; + this.licenseExpiryDate = licenseExpiryDate; + this.readOnly = readOnly; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + RepoUsage that = (RepoUsage) obj; + return EqualsHelper.nullSafeEquals(this.users, that.users) && + EqualsHelper.nullSafeEquals(this.documents, that.documents) && + EqualsHelper.nullSafeEquals(this.licenseMode, that.licenseMode) && + EqualsHelper.nullSafeEquals(this.licenseExpiryDate, that.licenseExpiryDate) && + this.readOnly == that.readOnly; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(128); + sb.append("RepoUsage") + .append("[lastUpdate=").append(lastUpdate) + .append(", users=").append(users) + .append(", documents=").append(documents) + .append(", licenseMode=").append(licenseMode) + .append(", licenseExpiryDate=").append(licenseExpiryDate) + .append(", readOnly=").append(readOnly) + .append("]"); + return sb.toString(); + } + + /** + * Get the time (ms since epoch) that the repository usage was last updated. + * + * @return time of last usage update + */ + public Long getLastUpdate() + { + return lastUpdate; + } + + /** + * Get the number of users or null if unknown + * + * @return the number of users or null if unknown + */ + public Long getUsers() + { + return users; + } + + /** + * Get the number of documents or null if not known + * + * @return document count or null if not known + */ + public Long getDocuments() + { + return documents; + } + + /** + * Get the server license mode. This is determined by (a) the build in use and + * (b) the installed license. + * + * @return the license mode (never null) + */ + public LicenseMode getLicenseMode() + { + return licenseMode; + } + + /** + * Get the server license expiry date. This is determined by the license and is + * null if there is no expiry or if it is unknown. + * + * @return the license expiry date or null + */ + public Long getLicenseExpiryDate() + { + return licenseExpiryDate; + } + + /** + * Get the read-write state of the repository + * + * @return true if the server is in read-only mode otherwise false + */ + public boolean isReadOnly() + { + return readOnly; + } +} diff --git a/source/java/org/alfresco/service/cmr/admin/RepoUsageStatus.java b/source/java/org/alfresco/service/cmr/admin/RepoUsageStatus.java new file mode 100644 index 0000000000..9b419bd37b --- /dev/null +++ b/source/java/org/alfresco/service/cmr/admin/RepoUsageStatus.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.service.cmr.admin; + +import java.util.List; + +import org.apache.commons.logging.Log; + +/** + * Bean to carry Red/Amber/Green status messages + * + * @author Derek Hulley + * @since V3.4 Team + */ +public class RepoUsageStatus +{ + /** + * Enumeration of usage levels + * + * @author Derek Hulley + * @since V3.4 Team + */ + public enum RepoUsageLevel + { + OK, WARN_ADMIN, WARN_ALL, LOCKED_DOWN + } + + private RepoUsage restrictions; + private RepoUsage usage; + private final RepoUsageLevel level; + private final List warnings; + private final List errors; + + public RepoUsageStatus( + RepoUsage restrictions, RepoUsage usage, + RepoUsageLevel level, List warnings, List errors) + { + this.restrictions = restrictions; + this.usage = usage; + this.level = level; + this.warnings = warnings; + this.errors = errors; + } + + @Override + public String toString() + { + return "UsageStatus [level=" + level + ", warnings=" + warnings + ", errors=" + errors + "]"; + } + + /** + * Log warnings and errors to the given logger + */ + public void logMessages(Log logger) + { + for (String msg : warnings) + { + logger.warn(msg); + } + for (String msg : errors) + { + logger.error(msg); + } + } + + public RepoUsage getRestrictions() + { + return restrictions; + } + + public RepoUsage getUsage() + { + return usage; + } + + /** + * @return Returns the current warning level + */ + public RepoUsageLevel getLevel() + { + return level; + } + + /** + * @return Returns any warnings generated + */ + public List getWarnings() + { + return warnings; + } + + /** + * @return Returns any errors generated + */ + public List getErrors() + { + return errors; + } +} diff --git a/source/java/org/alfresco/service/cmr/model/FileFolderService.java b/source/java/org/alfresco/service/cmr/model/FileFolderService.java index 4852319f56..a2c2d3f315 100644 --- a/source/java/org/alfresco/service/cmr/model/FileFolderService.java +++ b/source/java/org/alfresco/service/cmr/model/FileFolderService.java @@ -26,6 +26,7 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; +import org.springframework.extensions.surf.util.I18NUtil; /** * Provides methods specific to manipulating {@link org.alfresco.model.ContentModel#TYPE_CONTENT files} @@ -78,6 +79,19 @@ public interface FileFolderService @Auditable(parameters = {"contextNodeRef"}) public List listDeepFolders(NodeRef contextNodeRef, SubFolderFilter filter); + /** + * Uses the cm:name of the given node and attempts to find a sibling node + * with a more specific localized name. The node passed in must represent the base + * of the possible translations i.e. the base name for the resource names will be + * calculated using the filename without extension. The locale used will come from + * {@link I18NUtil#getLocale() the thread's default locale}. + * + * @param nodeRef the node that acts as the baseline for the search + * @return Returns a sibling node or the original node + */ + @Auditable(parameters = ("nodeRef")) + public NodeRef getLocalizedSibling(NodeRef nodeRef); + /** * Get a node ref of the node that has the name within the parent node * @@ -88,7 +102,6 @@ public interface FileFolderService @Auditable(parameters = {"contextNodeRef", "name"}) public NodeRef searchSimple(NodeRef contextNodeRef, String name); - /** * Searches for all files and folders with the matching name pattern, * using wildcard characters * and ?. @@ -175,9 +188,12 @@ public interface FileFolderService * Move a file or folder to a new name and/or location. *

    * If both the parent folder and name remain the same, then nothing is done. + *

    + * It is possible to specify which is the parent node when moving nodes; nodes + * can reside in multiple locations. * * @param sourceNodeRef the file or folder to move - * @param sourceParentRef the source parent of node - null means move from primary parent + * @param sourceParentRef the source parent of node - null means move from primary parent * @param targetParentRef the new parent node to move the node to - null means rename in situ * @param newName the name to change the file or folder to - null to keep the existing name * @return Returns the new file info @@ -185,6 +201,14 @@ public interface FileFolderService * @throws FileNotFoundException */ @Auditable(parameters = { "sourceNodeRef", "sourceParentRef", "targetParentRef", "newName" }) + public FileInfo moveFrom(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException; + + /** + * @deprecated From 3.4.2, use {@link #moveFrom(NodeRef, NodeRef, NodeRef, String)} or + * {@link #move(NodeRef, NodeRef, String)}. See + * ALF-7692 + */ + @Auditable(parameters = { "sourceNodeRef", "sourceParentRef", "targetParentRef", "newName" }) public FileInfo move(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException; /** diff --git a/source/java/org/alfresco/service/cmr/rating/RatingScheme.java b/source/java/org/alfresco/service/cmr/rating/RatingScheme.java index 2b94cd4a4f..1b2072a009 100644 --- a/source/java/org/alfresco/service/cmr/rating/RatingScheme.java +++ b/source/java/org/alfresco/service/cmr/rating/RatingScheme.java @@ -19,6 +19,9 @@ package org.alfresco.service.cmr.rating; +import java.util.List; + +import org.alfresco.repo.rating.AbstractRatingRollupAlgorithm; import org.alfresco.repo.rating.RatingSchemeRegistry; /** @@ -53,4 +56,22 @@ public interface RatingScheme * @return the maximum rating. */ public float getMaxRating(); + + /** + * This method returns true if the cm:creator of the node is allowed + * to apply a rating to it, else false. + * + * @return whether or not the cm:creator of the node can apply a rating in this scheme. + */ + public boolean isSelfRatingAllowed(); + + /** + * This method returns a List of {@link AbstractRatingRollupAlgorithm property rollup algorithms} + * which are used in order to calculate rating totals, counts etc for a rated node. + * + * @return an unmodifiable list of property rollup algorithms. + * @since 3.5 + */ + public List getPropertyRollups(); + } diff --git a/source/java/org/alfresco/service/cmr/rating/RatingService.java b/source/java/org/alfresco/service/cmr/rating/RatingService.java index 69226bb200..e4af169cd6 100644 --- a/source/java/org/alfresco/service/cmr/rating/RatingService.java +++ b/source/java/org/alfresco/service/cmr/rating/RatingService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -19,8 +19,11 @@ package org.alfresco.service.cmr.rating; +import java.io.Serializable; +import java.util.List; import java.util.Map; +import org.alfresco.repo.rating.AbstractRatingRollupAlgorithm; import org.alfresco.service.NotAuditable; import org.alfresco.service.PublicService; import org.alfresco.service.cmr.repository.NodeRef; @@ -64,18 +67,13 @@ public interface RatingService /** * This method applies the given rating to the specified target node. If a rating * from the current user in the specified scheme already exists, it will be replaced. - *

    - * Note that only one rating scheme per user per targetNode is supported at present. - * If a user attempts to apply a second rating in a different rating scheme to any given - * target node, a {@link RatingServiceException} will be thrown. * * @param targetNode the node to which the rating is to be applied. * @param rating the rating which is to be applied. * @param ratingSchemeName the name of the rating scheme to use. * * @throws RatingServiceException if the rating is not within the range defined by the named scheme - * or if the named scheme is not registered or if the rating would result - * in multiple ratings by the same user. + * or if the named scheme is not registered. * @see RatingService#getRatingSchemes() * @see RatingScheme */ @@ -136,6 +134,20 @@ public interface RatingService @NotAuditable Rating getRatingByCurrentUser(NodeRef targetNode, String ratingSchemeName); + /** + * This method gets the {@link Rating ratings} applied by the current user to the specified node. + * + * @param targetNode the node on which the ratings are sought. + * + * @return a List of Rating objects if there are any, else {@link java.util.Collections#emptyList()}. + * @see RatingService#getRatingSchemes() + * @see RatingScheme + * + * @since 3.5 + */ + @NotAuditable + List getRatingsByCurrentUser(NodeRef targetNode); + /** * This method removes any {@link Rating} applied by the current user to the specified node in the specified * {@link RatingScheme}. @@ -149,4 +161,27 @@ public interface RatingService */ @NotAuditable Rating removeRatingByCurrentUser(NodeRef targetNode, String ratingSchemeName); + + /** + * This method returns a 'rolled up' property value for the specified targetNode. Examples + * of rolled up property values are 'ratingTotal', 'ratingCount', but other values can be added. + *

    + * Rolled up properties in the RatingService are stored as properties on the rated node and have their values + * calculated by running a fixed algorithm on properties stored across the cm:rating child nodes. + * An example of such a roll up would be 'ratingTotal' which would be the sum of all ratings applied to the + * targetNode in the specified rating scheme. + *

    + * By rolling up property values from the various cm:rating child nodes and persisting them + * as individual properties on the cm:rateable node itself, we are able to support indexing, searching + * and sorting of such properties. + * + * @param targetNode the rated node whose rolled up property we wish to read. + * @param ratingSchemeName the rating scheme name in which the property is relevant. + * @param ratingRollupName the name of the rating rollup property, as given in {@link AbstractRatingRollupAlgorithm#getRollupName()}. + * @return A value for the rolled up property, which will depend of course on the rollup requested. + * + * @since 3.5 + */ + @NotAuditable + Serializable getRatingRollup(NodeRef targetNode, String ratingSchemeName, String ratingRollupName); } diff --git a/source/java/org/alfresco/service/cmr/rendition/RenditionService.java b/source/java/org/alfresco/service/cmr/rendition/RenditionService.java index 7e0bb98896..7754314f63 100644 --- a/source/java/org/alfresco/service/cmr/rendition/RenditionService.java +++ b/source/java/org/alfresco/service/cmr/rendition/RenditionService.java @@ -210,4 +210,43 @@ public interface RenditionService extends RenditionDefinitionPersister @NotAuditable void render(NodeRef sourceNode, RenditionDefinition renditionDefinition, RenderCallback callback); + + /** + * This method synchronously renders content as specified by the given + * {@link RenditionDefinition#getRenditionName() rendition name}. + * The content to be rendered is provided by the specified source node. + *

    + * The Rendition Definition will be loaded from the standard location as system + * thus allowing rendition definitions to be used even when the Data Dictionary + * has restricted read access. + * + * @param sourceNode the node from which the content is retrieved. + * @param renditionDefinitionQName the rendition definition which is to + * be performed. + * @return a child association reference which is the link from the source + * node to the newly rendered content. + * @throws RenditionServiceException if there is a problem in rendering the + * given sourceNode + * @since 3.4.2 + */ + @NotAuditable + ChildAssociationRef render(NodeRef sourceNode, QName renditionDefinitionQName); + + /** + * This method asynchronously renders content as specified by the given + * {@link RenditionDefinition#getRenditionName() rendition definition name}. + * The content to be rendered is provided by the specified source node. + *

    + * The Rendition Definition will be loaded from the standard location as system + * thus allowing rendition definitions to be used even when the Data Dictionary + * has restricted read access. + * + * @param sourceNode the node from which the content is retrieved. + * @param renditionDefinitionQName the rendition definition which is to + * be performed. + * @param callback a callback object to handle the ultimate result of the rendition. + * @since 3.4.2 + */ + @NotAuditable + void render(NodeRef sourceNode, QName renditionDefinitionQName, RenderCallback callback); } diff --git a/source/java/org/alfresco/service/cmr/rendition/RenditionServiceException.java b/source/java/org/alfresco/service/cmr/rendition/RenditionServiceException.java index a19b018478..733d532afa 100644 --- a/source/java/org/alfresco/service/cmr/rendition/RenditionServiceException.java +++ b/source/java/org/alfresco/service/cmr/rendition/RenditionServiceException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -28,25 +28,66 @@ import org.alfresco.error.AlfrescoRuntimeException; public class RenditionServiceException extends AlfrescoRuntimeException { private static final long serialVersionUID = -6947067735970465937L; + private final RenditionDefinition renditionDefinition; /** - * Constructs a Rendition Service Exception with the specified message. - * - * @param message the message string - */ - public RenditionServiceException(String message) - { - super(message); - } + * Constructs a Rendition Service Exception with the specified message. + * + * @param message the message string + */ + public RenditionServiceException(String message) + { + super(message); + this.renditionDefinition = null; + } /** * Constructs a Rendition Service Exception with the specified message and source exception. * * @param message the message string - * @param source the source exception - */ - public RenditionServiceException(String message, Throwable source) - { - super(message, source); - } + * @param source the source exception + */ + public RenditionServiceException(String message, Throwable source) + { + super(message, source); + this.renditionDefinition = null; + } + + /** + * Constructs a Rendition Service Exception with the specified message and {@link RenditionDefinition}. + * + * @param message the message string. + * @param renditionDefinition the rendition definition. + * @since 3.5.0 + */ + public RenditionServiceException(String message, RenditionDefinition renditionDefinition) + { + super(message); + this.renditionDefinition = renditionDefinition; + } + + /** + * Constructs a Rendition Service Exception with the specified message, {@link RenditionDefinition} and + * source exception + * . + * @param message the message string. + * @param renditionDefinition the rendition definition. + * @param source the source exception. + * @since 3.5.0 + */ + public RenditionServiceException(String message, RenditionDefinition renditionDefinition, Throwable source) + { + super(message, source); + this.renditionDefinition = renditionDefinition; + } + + /** + * Retrieves the {@link RenditionDefinition} associated with this exception. + * @return the rendition definition, which may be null. + * @since 3.5.0 + */ + public RenditionDefinition getRenditionDefinition() + { + return this.renditionDefinition; + } } diff --git a/source/java/org/alfresco/service/cmr/replication/ReplicationService.java b/source/java/org/alfresco/service/cmr/replication/ReplicationService.java index f74ad7775d..b73200f1c8 100644 --- a/source/java/org/alfresco/service/cmr/replication/ReplicationService.java +++ b/source/java/org/alfresco/service/cmr/replication/ReplicationService.java @@ -58,4 +58,9 @@ public interface ReplicationService extends ReplicationDefinitionPersister { */ @NotAuditable void disableScheduling(ReplicationDefinition replicationDefinition); + + /** + * Is the replication service enabled? + */ + boolean isEnabled(); } diff --git a/source/java/org/alfresco/service/cmr/repository/ContentService.java b/source/java/org/alfresco/service/cmr/repository/ContentService.java index 0d4544b19d..0509ce5654 100644 --- a/source/java/org/alfresco/service/cmr/repository/ContentService.java +++ b/source/java/org/alfresco/service/cmr/repository/ContentService.java @@ -18,6 +18,7 @@ */ package org.alfresco.service.cmr.repository; +import java.util.List; import java.util.Map; import org.alfresco.repo.content.transform.ContentTransformer; @@ -227,8 +228,8 @@ public interface ContentService * Fetch the transformer that is capable of transforming the content in the * given source mimetype to the given target mimetype with the provided transformation * options. - *

    - * The transformation options provide a finer grain way of discoving the correct transformer, + *

    + * The transformation options provide a finer grain way of discovering the correct transformer, * since the values and type of the options provided are considered by the transformer when * deciding whether it can satisfy the transformation request. * @@ -242,6 +243,30 @@ public interface ContentService @Auditable(parameters = {"sourceMimetype", "targetMimetype", "options"}) public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype, TransformationOptions options); + /** + * Fetch all the transformers that are capable of transforming the content in the + * given source mimetype to the given target mimetype with the provided transformation + * options. + *

    + * The transformation options provide a finer grain way of discovering the correct transformer, + * since the values and type of the options provided are considered by the transformer when + * deciding whether it can satisfy the transformation request. + *

    + * The list will contain all currently active, applicable transformers sorted in repository preference order. + * The contents of this list may change depending on such factors as the availability of particular transformers + * as well as their current behaviour. For these reasons, this list should not be cached. + * + * @param sourceMimetype the source mimetype + * @param targetMimetype the target mimetype + * @param options the transformation options + * @return ContentTransformers a List of the transformers that can be used, or the empty list if none were available + * + * @since 3.5 + * @see ContentAccessor#getMimetype() + */ + @Auditable(parameters = {"sourceMimetype", "targetMimetype", "options"}) + public List getActiveTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options); + /** * Fetch the transformer that is capable of transforming image content. * diff --git a/source/java/org/alfresco/service/cmr/repository/TemplateService.java b/source/java/org/alfresco/service/cmr/repository/TemplateService.java index 0bb7a5da08..f4df7bd878 100644 --- a/source/java/org/alfresco/service/cmr/repository/TemplateService.java +++ b/source/java/org/alfresco/service/cmr/repository/TemplateService.java @@ -48,6 +48,8 @@ public interface TemplateService public static final String KEY_PERSON = "person"; public static final String KEY_TEMPLATE = "template"; public static final String KEY_DATE = "date"; + public static final String KEY_SHARE_URL = "shareUrl"; + public static final String KEY_PRODUCT_NAME = "productName"; /** * Process a template against the upplied data model and return the result as diff --git a/source/java/org/alfresco/service/cmr/security/AuthorityService.java b/source/java/org/alfresco/service/cmr/security/AuthorityService.java index 7845cc79a5..1018783815 100644 --- a/source/java/org/alfresco/service/cmr/security/AuthorityService.java +++ b/source/java/org/alfresco/service/cmr/security/AuthorityService.java @@ -320,6 +320,16 @@ public interface AuthorityService @Auditable(parameters = {"authorityName", "authorityDisplayName"}) public void setAuthorityDisplayName(String authorityName, String authorityDisplayName); + /** + * Gets the authority node for the specified name + * + * @param name The authority name + * + * @return the reference to the authority node + */ + @Auditable(parameters = {"name"}) + public NodeRef getAuthorityNodeRef(String name); + /** * Gets or creates an authority zone node with the specified name * diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index c21f34a5f6..1d9b2c7cbe 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -177,6 +177,21 @@ public interface PersonService @Auditable(parameters = {"properties", "zones"}) public NodeRef createPerson(Map properties, Set zones); + /** + * Notifies a user by email that their account has been created, and the details of it. + * Normally called after {@link #createPerson(Map)} or {@link #createPerson(Map, Set)} + * where email notifications are required. + * + * @param userName + * of the person to notify + * @param password + * of the person to notify + * @throws NoSuchPersonException + * if the person doesn't exist + */ + @Auditable(parameters = {"userName"}) + public void notifyPerson(final String userName, final String password); + /** * Delete the person identified by the given user name. * diff --git a/source/java/org/alfresco/service/cmr/thumbnail/FailedThumbnailInfo.java b/source/java/org/alfresco/service/cmr/thumbnail/FailedThumbnailInfo.java new file mode 100644 index 0000000000..9f0d03049a --- /dev/null +++ b/source/java/org/alfresco/service/cmr/thumbnail/FailedThumbnailInfo.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2005-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 . + */ +package org.alfresco.service.cmr.thumbnail; + +import java.util.Date; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.thumbnail.CreateThumbnailActionExecuter; +import org.alfresco.repo.thumbnail.ThumbnailDefinition; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * A simple pojo to hold data related to a {@link ContentModel#TYPE_FAILED_THUMBNAIL failed thumbnail attempt}. + * A failed thumbnail attempt is when {@link CreateThumbnailActionExecuter create-thumbnail} has been used + * to produce a thumbnail for content and that action has thrown an exception. + * If a thumbnail was not attempted (e.g. due to unavailability of transformers) this is not a failure in this context. + * + * @author Neil Mc Erlean + * @since 3.5.0 + */ +public class FailedThumbnailInfo +{ + private final String thumbnailDefinitionName; + private final Date mostRecentFailure; + private final int failureCount; + private final NodeRef failedThumbnailNode; + + public FailedThumbnailInfo(String thumbnailDefinitionName, Date failureDate, + int failureCount, NodeRef failedThumbnailNode) + { + this.thumbnailDefinitionName = thumbnailDefinitionName; + this.mostRecentFailure = failureDate; + this.failureCount = failureCount; + this.failedThumbnailNode = failedThumbnailNode; + } + + /** + * Get the {@link ThumbnailDefinition#getName() thumbnail definition name} that has failed. + */ + public String getThumbnailDefinitionName() + { + return thumbnailDefinitionName; + } + + /** + * Get the time of the most recent failure. + */ + public Date getMostRecentFailure() + { + return mostRecentFailure; + } + + /** + * Get the total number of failed attempts which have been made to produce a thumbnail. + * @return + */ + public int getFailureCount() + { + return failureCount; + } + + /** + * Get the {@link NodeRef} of the {@link ContentModel#TYPE_FAILED_THUMBNAIL failedThumbnail} node. + * Note that this is not the NodeRef which was not thumbnailed - that will be the primary parent of + * this node. + */ + public NodeRef getFailedThumbnailNode() + { + return failedThumbnailNode; + } +} diff --git a/source/java/org/alfresco/service/cmr/thumbnail/ThumbnailService.java b/source/java/org/alfresco/service/cmr/thumbnail/ThumbnailService.java index 29f84b39d9..35995bdf16 100644 --- a/source/java/org/alfresco/service/cmr/thumbnail/ThumbnailService.java +++ b/source/java/org/alfresco/service/cmr/thumbnail/ThumbnailService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -19,7 +19,9 @@ package org.alfresco.service.cmr.thumbnail; import java.util.List; +import java.util.Map; +import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.repo.thumbnail.ThumbnailRegistry; import org.alfresco.service.Auditable; import org.alfresco.service.NotAuditable; @@ -134,4 +136,16 @@ public interface ThumbnailService @Auditable(parameters = {"node", "contentProperty", "mimetype", "options"}) List getThumbnails(NodeRef node, QName contentProperty, String mimetype, TransformationOptions options); + /** + * This method returns a {@link Map} of {@link FailedThumbnailInfo failed thumbnails} for the specified source node. + * The map is keyed by {@link ThumbnailDefinition#getName() thumbnail definition name} + * and the values are the {@link FailedThumbnailInfo failed thumbnails}. + * + * @param sourceNode the node whose thumbnails are to be checked. + * @return a Map of failed thumbnails, if any. If there + * are no such failures, an empty Map will be returned. + * @since 3.5.0 + */ + @Auditable(parameters = {"sourceNode"}) + Map getFailedThumbnails(NodeRef sourceNode); } diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferReceiver.java b/source/java/org/alfresco/service/cmr/transfer/TransferReceiver.java index 946cc395f5..237439ea7e 100644 --- a/source/java/org/alfresco/service/cmr/transfer/TransferReceiver.java +++ b/source/java/org/alfresco/service/cmr/transfer/TransferReceiver.java @@ -50,10 +50,11 @@ public interface TransferReceiver * Asks the receiver to setup a new transfer. * @param fromRepositoryId the repositoryId of the sending system * @param allowTransferToSelf are transfers to the same repository allowed? + * @param fromVersion the version sending * @return The identifier of the new transfer * @throws TransferException if an error occurred while setting up the transfer */ - String start(String fromRepositoryId, boolean allowTransferToSelf) throws TransferException; + String start(String fromRepositoryId, boolean allowTransferToSelf, TransferVersion fromVersion) throws TransferException; /** * Asks the receiver to end (and clean up) the specified transfer @@ -125,6 +126,11 @@ public interface TransferReceiver */ TransferProgress getStatus(String transferId) throws TransferException; + /** + * Get the version that we are transfering to. + */ + TransferVersion getVersion(); + /** * * @return diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferVersion.java b/source/java/org/alfresco/service/cmr/transfer/TransferVersion.java new file mode 100644 index 0000000000..7ba49e17fe --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferVersion.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.cmr.transfer; + +public interface TransferVersion +{ + /** + * Gets the major version number, e.g. 1.2.3 + * + * @return major version number + */ + public String getVersionMajor(); + + /** + * Gets the minor version number, e.g. 1.2.3 + * + * @return minor version number + */ + public String getVersionMinor(); + + /** + * Gets the version revision number, e.g. 1.2.3 + * + * @return revision number + */ + public String getVersionRevision(); + + /** + * Gets the edition + * + * @return the edition + */ + public String getEdition(); + +} diff --git a/source/java/org/alfresco/service/cmr/version/VersionService.java b/source/java/org/alfresco/service/cmr/version/VersionService.java index 483ea25f8f..fa28165070 100644 --- a/source/java/org/alfresco/service/cmr/version/VersionService.java +++ b/source/java/org/alfresco/service/cmr/version/VersionService.java @@ -117,7 +117,7 @@ public interface VersionService * @param versionProperties version property values * @return a collection of newly created versions * @throws ReservedVersionNameException - * thrown if a reserved property name is used int he version properties + * thrown if a reserved property name is used in the version properties * provided * @throws AspectMissingException * thrown if the version aspect is missing @@ -293,6 +293,20 @@ public interface VersionService @Auditable(parameters = {"nodeRef", "version"}) public void deleteVersion(NodeRef nodeRef, Version version); + /** + * Ensures that a node has the versionable aspect applied to it, and has + * at least an initial entry in the version store. + * If any of these requirements are missing, then they will be fixed. + * + * @param nodeRef the node reference + * @param versionProperties the version properties to apply if versioning + * isn't currently enabled for the node + */ + @Auditable(parameters = {"nodeRef"}) + public void ensureVersioningEnabled( + NodeRef nodeRef, + Map versionProperties); + /** * Register a version label policy * diff --git a/source/java/org/alfresco/service/cmr/view/AVMZipExporterService.java b/source/java/org/alfresco/service/cmr/view/AVMZipExporterService.java new file mode 100644 index 0000000000..df47035d6a --- /dev/null +++ b/source/java/org/alfresco/service/cmr/view/AVMZipExporterService.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.service.cmr.view; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipException; + +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Exporter which allows the saving of part of an AVM + * filesystem to a Zip file. + * + * @author Nick Burch + */ +public interface AVMZipExporterService +{ + /** + * Exports the given path and version as a zip file, stored + * in the specified file. + * + * @param output The File to store the Zip in + * @param path The AVM path to export + * @param version The AVM version IO + * @param recurse Should the export recurse into directories? + */ + public void export(File output, int version, String path, boolean recurse) + throws IOException, ZipException; + + /** + * Exports the given path and version into an already open + * Zip file. This method can be used to output multiple different + * AVM resources into one file. + * + * @param output The File to store the Zip in + * @param path The AVM path to export + * @param version The AVM version IO + * @param recurse Should the export recurse into directories? + */ + public void export(ZipOutputStream out, int version, String path, boolean recurse) + throws IOException, ZipException; + + /** + * Exports the given AVM node into an already open + * Zip file. This method can be used to output multiple different + * AVM resources into one file. + * + * @param output The File to store the Zip in + * @param node The AVM node to export + * @param recurse Should the export recurse into directories? + */ + public void export(ZipOutputStream out, AVMNodeDescriptor node, boolean recurse) + throws IOException, ZipException; +} diff --git a/source/java/org/alfresco/service/descriptor/Descriptor.java b/source/java/org/alfresco/service/descriptor/Descriptor.java index a6b3552082..ac9a42f572 100644 --- a/source/java/org/alfresco/service/descriptor/Descriptor.java +++ b/source/java/org/alfresco/service/descriptor/Descriptor.java @@ -18,6 +18,7 @@ */ package org.alfresco.service.descriptor; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; import org.alfresco.util.VersionNumber; @@ -96,6 +97,13 @@ public interface Descriptor */ public String getEdition(); + /** + * Gets LicenseMode + * + * @return the licenseMode + */ + public LicenseMode getLicenseMode(); + /** * Gets the schema number * diff --git a/source/java/org/alfresco/service/descriptor/DescriptorService.java b/source/java/org/alfresco/service/descriptor/DescriptorService.java index 59f111db2d..8c3d93715b 100644 --- a/source/java/org/alfresco/service/descriptor/DescriptorService.java +++ b/source/java/org/alfresco/service/descriptor/DescriptorService.java @@ -32,7 +32,27 @@ import org.alfresco.service.license.LicenseDescriptor; public interface DescriptorService { /** - * Get descriptor for the server + * Get descriptor for the alfresco software installed on the server. + *

    + * The information contained by this descriptor is read from a property file. + * + * The following properties are available in the descriptor + *

      + *
    • Major
    • + *
    • Minor
    • + *
    • Revision
    • + *
    • Label
    • + *
    • Build
    • + *
    • Edition
    • + *
    • Schema
    • + *
    + * + * The following properties are not applicable to the server descriptor. + *
      + *
    • id
    • + *
    • licenceKey
    • + *
    • name - unknown
    • + *
    * * @return server descriptor */ @@ -40,10 +60,30 @@ public interface DescriptorService public Descriptor getServerDescriptor(); /** - * Get descriptor for the repository as it is currently installed. + * Get current descriptor for the repository. + *

    + * The information in this descriptor is read from a node in the system store. After the patch process runs successfully, the version Major/Minor/Revision should + * be equal to the server descriptor. + *

    + * The "repository id" that uniquely identifies each alfresco repository is available in the "id" property. + *

    + * The following properties are available in the descriptor + *

      + *
    • Major
    • + *
    • Minor
    • + *
    • Revision
    • + *
    • Label
    • + *
    • Build
    • + *
    • Schema
    • + *
    • name
    • + *
    • id
    • + *
    * - * The current repository descriptor will always be the same as the - * {@link #getServerDescriptor() server descriptor}. + * The following properties may be present + *
      + *
    • LicenceKey
    • + *
    • Edition
    • + *
    * * @return repository descriptor */ @@ -52,6 +92,8 @@ public interface DescriptorService /** * Get descriptor for the repository as it was when first installed. + *

    + * The information in this descriptor is read from a node in the system store. * * @return repository descriptor */ @@ -65,4 +107,10 @@ public interface DescriptorService */ @NotAuditable public LicenseDescriptor getLicenseDescriptor(); + + /** + * Attempts to load the license. + * @return + */ + public String loadLicense(); } diff --git a/source/java/org/alfresco/service/license/LicenseDescriptor.java b/source/java/org/alfresco/service/license/LicenseDescriptor.java index 17de6d8d64..7a3921f5a1 100644 --- a/source/java/org/alfresco/service/license/LicenseDescriptor.java +++ b/source/java/org/alfresco/service/license/LicenseDescriptor.java @@ -21,6 +21,8 @@ package org.alfresco.service.license; import java.security.Principal; import java.util.Date; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; + /** * Provides access to License information. * @@ -91,4 +93,22 @@ public interface LicenseDescriptor * @return a URL or null */ public String getHeartBeatUrl(); + + /** + * Gets the maximum number of documents. + * @return the maximum number of documents or null if there is no limit + */ + public Long getMaxDocs(); + + /** + * Gets the maximum number of users. + * @return the maximum number of users or null if there is no limit + */ + public Long getMaxUsers(); + + /** + * Get the license mode e.g TEAM or ENTERPRISE or any future license mode. + * @return the license mode. + */ + public LicenseMode getLicenseMode(); } diff --git a/source/java/org/alfresco/service/license/LicenseService.java b/source/java/org/alfresco/service/license/LicenseService.java index 4634aa3fb4..04e3917aaa 100644 --- a/source/java/org/alfresco/service/license/LicenseService.java +++ b/source/java/org/alfresco/service/license/LicenseService.java @@ -29,7 +29,11 @@ import org.alfresco.service.PublicService; @PublicService public interface LicenseService { - + /** + * Force license reload + */ + public String loadLicense(); + /** * Begin the license verification loop. Throws an exception if a new .lic file has been supplied that is invalid. * Will quietly make the repository read only if there is no license and the repository isn't eligible for the free @@ -55,10 +59,35 @@ public interface LicenseService */ @NotAuditable public LicenseDescriptor getLicense(); - + + /** + * Register a callback that gets called when a license changes. + */ + public void registerOnLicenseChange(LicenseChangeHandler callback); + /** * Informs the service it is being shutdown. */ @NotAuditable public void shutdown(); + + /** + * Inteface for components wishing to know when the license has changed + * + * @see registerOnLicenseChange + */ + public interface LicenseChangeHandler + { + /** + * Notification of a license change. + * + * @param licenseDescriptor the new license (never null) + */ + void onLicenseChange(LicenseDescriptor licenseDescriptor); + + /** + * Notification that a license have failed to validate + */ + void onLicenseFail(); + } } diff --git a/source/java/org/alfresco/service/transaction/TransactionService.java b/source/java/org/alfresco/service/transaction/TransactionService.java index 6b98503657..b56b072c5f 100644 --- a/source/java/org/alfresco/service/transaction/TransactionService.java +++ b/source/java/org/alfresco/service/transaction/TransactionService.java @@ -37,9 +37,20 @@ import org.alfresco.service.PublicService; public interface TransactionService { /** - * Determine if ALL user transactions will be read-only. + * Determine if the repository has been put into read only mode. + * This is independent of the current user. * - * @return Returns true if all transactions are read-only. + * @return true if the repository is allowed to perform + * write operations + */ + public boolean getAllowWrite(); + + /** + * Determine if ALL user transactions will be read-only. The 'System' + * user is always allowed to write. + * + * @return Returns true if all transactions are read-only AND the current + * user is not the 'System' user. */ @NotAuditable public boolean isReadOnly(); @@ -65,6 +76,18 @@ public interface TransactionService @NotAuditable UserTransaction getUserTransaction(boolean readOnly); + /** + * Gets a user transaction that supports transaction propagation. + * This is like the EJB REQUIRED transaction attribute. + * + * @param readOnly Set true for a READONLY transaction instance, false otherwise. + * @param ignoreSystemReadOnly true to force the read-only flag to be respected regardless + * of the system read-only mode. + * @return the user transaction + */ + @NotAuditable + UserTransaction getUserTransaction(boolean readOnly, boolean ignoreSystemReadOnly); + /** * Gets a user transaction that ensures a new transaction is created. * Any enclosing transaction is not propagated. @@ -87,11 +110,26 @@ public interface TransactionService * @param readOnly Set true for a READONLY transaction instance, false otherwise. * Note that it is not always possible to force a write transaction if the * system is in read-only mode. - * @return Returns a non-gating user transaction + * @return Returns a non-propagating user transaction */ @NotAuditable UserTransaction getNonPropagatingUserTransaction(boolean readOnly); + /** + * Gets a user transaction that ensures a new transaction is created. + * Any enclosing transaction is not propagated. + * This is like the EJB REQUIRES_NEW transaction attribute - + * when the transaction is started, the current transaction will be + * suspended and a new one started. + * + * @param readOnly Set true for a READONLY transaction instance, false otherwise. + * @param ignoreSystemReadOnly true to force the read-only flag to be respected regardless + * of the system read-only mode. + * @return Returns a non-propagating user transaction + */ + @NotAuditable + UserTransaction getNonPropagatingUserTransaction(boolean readOnly, boolean ignoreSystemReadOnly); + /** * Get the standard instance of the helper object that supports transaction retrying. * diff --git a/source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutorTest.java b/source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutorTest.java index f26ccd1aa1..5a35f3e70e 100644 --- a/source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutorTest.java +++ b/source/java/org/alfresco/util/DynamicallySizedThreadPoolExecutorTest.java @@ -164,7 +164,7 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase assertEquals(4, exec.getPoolSize()); } - public void testToExpandThenContract() throws Exception + public void offTestToExpandThenContract() throws Exception { factory.setCorePoolSize(2); factory.setMaximumPoolSize(4); diff --git a/source/java/org/alfresco/util/ModelUtil.java b/source/java/org/alfresco/util/ModelUtil.java new file mode 100644 index 0000000000..e3b3cf9a18 --- /dev/null +++ b/source/java/org/alfresco/util/ModelUtil.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-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 . + */ + +package org.alfresco.util; + +import org.alfresco.service.cmr.admin.RepoAdminService; +import org.alfresco.service.cmr.admin.RepoUsage.LicenseMode; + + +/** + * Model related utility functions. + * + * @since 3.5 + */ +public class ModelUtil +{ + private static final String SHARE = "Share"; + private static final String TEAM = "Team"; + + /** + * Returns the name of the product currently running, determined + * by the current license. + * + * @param repoAdminService The RepoAdminService + * @return "Share" or "Team" + */ + public static String getProductName(RepoAdminService repoAdminService) + { + // the product name is never localised so it's safe to + // return a hard-coded string but if we ever need to it's + // centralised here. + + String productName = SHARE; + + if (repoAdminService != null && + repoAdminService.getRestrictions().getLicenseMode().equals(LicenseMode.TEAM)) + { + productName = TEAM; + } + + return productName; + } +} diff --git a/source/java/org/alfresco/util/PathUtil.java b/source/java/org/alfresco/util/PathUtil.java new file mode 100644 index 0000000000..6fbf269e9f --- /dev/null +++ b/source/java/org/alfresco/util/PathUtil.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-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 . + */ + +package org.alfresco.util; + +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.Path; + +/** + * Alfresco path-related utility functions. + * + * @since 3.5 + */ +public class PathUtil +{ + /** + * Return the human readable form of the specified node Path. Fast version + * of the method that simply converts QName localname components to Strings. + * + * @param path Path to extract readable form from + * @param showLeaf Whether to process the final leaf element of the path + * + * @return human readable form of the Path + */ + public static String getDisplayPath(Path path, boolean showLeaf) + { + // This method was moved here from org.alfresco.web.bean.repository.Repository + StringBuilder buf = new StringBuilder(64); + + int count = path.size() - (showLeaf ? 0 : 1); + for (int i = 0; i < count; i++) + { + String elementString = null; + Path.Element element = path.get(i); + if (element instanceof Path.ChildAssocElement) + { + ChildAssociationRef elementRef = ((Path.ChildAssocElement) element).getRef(); + if (elementRef.getParentRef() != null) + { + elementString = elementRef.getQName().getLocalName(); + } + } else + { + elementString = element.getElementString(); + } + + if (elementString != null) + { + buf.append("/"); + buf.append(elementString); + } + } + + return buf.toString(); + } +} diff --git a/source/java/org/alfresco/util/UrlUtil.java b/source/java/org/alfresco/util/UrlUtil.java new file mode 100644 index 0000000000..0bfe1eef5c --- /dev/null +++ b/source/java/org/alfresco/util/UrlUtil.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005-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 . + */ + +package org.alfresco.util; + +import org.alfresco.repo.admin.SysAdminParams; + + +/** + * Alfresco URL related utility functions. + * + * @since 3.5 + */ +public class UrlUtil +{ + /** + * Builds up the Url to Alfresco based on the settings in the + * {@link SysAdminParams}. + * @return Alfresco Url such as https://col.ab.or.ate/alfresco/ + * or http://localhost:8080/alfresco/ + */ + public static String getAlfrescoUrl(SysAdminParams sysAdminParams) + { + return buildUrl( + sysAdminParams.getAlfrescoProtocol(), + sysAdminParams.getAlfrescoHost(), + sysAdminParams.getAlfrescoPort(), + sysAdminParams.getAlfrescoContext()); + } + + /** + * Builds up the Url to Share based on the settings in the + * {@link SysAdminParams}. + * @return Alfresco Url such as https://col.ab.or.ate/share/ + * or http://localhost:8081/share/ + */ + public static String getShareUrl(SysAdminParams sysAdminParams) + { + return buildUrl( + sysAdminParams.getShareProtocol(), + sysAdminParams.getShareHost(), + sysAdminParams.getSharePort(), + sysAdminParams.getShareContext()); + } + + protected static String buildUrl(String protocol, String host, int port, String context) + { + StringBuilder url = new StringBuilder(); + url.append(protocol); + url.append("://"); + url.append(host); + if ("http".equals(protocol) && port == 80) + { + // Not needed + } + else if ("https".equals(protocol) && port == 443) + { + // Not needed + } + else + { + url.append(':'); + url.append(port); + } + url.append('/'); + url.append(context); + return url.toString(); + } +} diff --git a/source/java/org/alfresco/wcm/AbstractWCMServiceImplTest.java b/source/java/org/alfresco/wcm/AbstractWCMServiceImplTest.java index eefa0d2213..52f4286f2f 100644 --- a/source/java/org/alfresco/wcm/AbstractWCMServiceImplTest.java +++ b/source/java/org/alfresco/wcm/AbstractWCMServiceImplTest.java @@ -120,10 +120,19 @@ public class AbstractWCMServiceImplTest extends TestCase USER_ADMIN = AuthenticationUtil.getAdminUserName(); AuthenticationUtil.setFullyAuthenticatedUser(USER_ADMIN); - createUser(USER_ONE); - createUser(USER_TWO); - createUser(USER_THREE); - createUser(USER_FOUR); + RetryingTransactionCallback createUsersCallback = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + createUser(USER_ONE); + createUser(USER_TWO); + createUser(USER_THREE); + createUser(USER_FOUR); + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(createUsersCallback); } @Override @@ -153,10 +162,19 @@ public class AbstractWCMServiceImplTest extends TestCase } } - deleteUser(USER_ONE); - deleteUser(USER_TWO); - deleteUser(USER_THREE); - deleteUser(USER_FOUR); + RetryingTransactionCallback deleteUsersCallback = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + deleteUser(USER_ONE); + deleteUser(USER_TWO); + deleteUser(USER_THREE); + deleteUser(USER_FOUR); + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(deleteUsersCallback); } AuthenticationUtil.clearCurrentSecurityContext(); diff --git a/source/test-resources/quick/quick-secured.pdf b/source/test-resources/quick/quick-secured.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5808edf0668b4d84c290a525eeade46c2cc5656a GIT binary patch literal 26967 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 UTAakj$_YS#B_k7+7ej#kH!Z{2KL7v# literal 0 HcmV?d00001 diff --git a/source/test-resources/quick/quickCorrupt.pdf b/source/test-resources/quick/quickCorrupt.pdf new file mode 100644 index 0000000000..70b7df6d04 --- /dev/null +++ b/source/test-resources/quick/quickCorrupt.pdf @@ -0,0 +1,2 @@ +%PDF-1.3 %���� +1 0 obj THIS IS ILLEGAL CONTENT WHICH SHOULD RENDER THE PDF UNREADABLE. endobj %%EOF \ No newline at end of file