From e89652d129860f596ac7cdb3fecf144ec3e49329 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Mon, 25 Mar 2013 09:37:19 +0000 Subject: [PATCH] Merged CONV_HEAD to HEAD 48422: Merged BRANCHES/DEV/CONV_V413 to BRANCHES/DEV/CONV_HEAD: 48397: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: 48391: Merged BRANCHES/DEV/FEATURES/CLOUD1-BUG-FIX2 to BRANCHES/DEV/CLOUD2: 48355: CLOUD-1458: Added MailActionExecuterTest to ActionTestSuite. 48398: CONV: CLOUD2 - fix for CLOUD-1459 - Site invite between Networks throws an preferences error (can't get locale for ano user) when the emails are about to be sent 48420: CONV: fix CLOUD-1351 - MultiTDemoTest.test21_ALF_12732 48532: Merged CONV_V413 to CONV_HEAD 46856: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 31046: THOR-206 IMplemented sharing of workflows across tenants for Activiti. 46896: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: 36063: Fix ActivitiMultitenantWorkflow test for cloud1 46897: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: 37559: Fix build test failure. 47181: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 35994: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 34517: Prevent 304 revalidations for unchanged thumbnails in document library, web preview and search 34607: Ensure folders created containing "#" don't send document library into infinite loop. 34615: Performance improvement: prevent unecessary 304 revalidation for user avatar thumbnails in header WebScript 34638: Performance improvement: prevent unnecessary 304 revalidation for avatars in activity feeds 34639: Performance improvement: prevent unnecessary 304 revalidation for avatars on following/follwers pages 34658: Performance improvement: prevent unnecessary 304 revalidation for avatars on site colleagues dashlet 34661: Merged BRANCHES/DEV/V4.0-BUG-FIX to BRANCHES/DEV/THOR1_SPRINTS 34636: Fix for ALF-13365 SOLR: Recently modified docs dashlet sorts incorrectly - respect short property names on sort requests @cm:created and not require the full @{uri...}created 34662: Refactored revalidation code to remove previously added WebScripts that are now surplus to requirements 34668: Fix build break 34680: Fixed bean config problem (caused by r34662) 34684: Performance improvement: prevent unnecessary 304 revalidation requests for thumbnails in detailed view of My-Documents and Recently Modified Documents dashlets 34701: Further refactoring of 304 revalidation code to ensure backwards compatibility 34723: Fix build break 36003: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 34895: Resolve THOR-1251: Text for Site URL field on Create Site Form Wrong 34897: Resolve THOR-1250: Text on Accept Invite Page Wrong 35036: Changed Upload REST API to no longer immediately request thumbnail for an uploaded document. Performance improvement as requested. (re ALF-1015) 35142: Merged BRANCHES/V4.0 to BRANCHES/DEV/THOR1_SPRINTS: 35013: ALF-13561: Not found error after uploading new version (THOR-1304) 35320: Fix to issue where 'Avatar' alt text was shown rather than user avatar in the Site Members (colleagues) dashlet. 35546: Improve upload.post.js to use the "filename" argument if it is passed to set the name of the file on upload. 36019: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36020: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36021: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36022: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36023: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36024: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36025: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 34819: THOR-1255 - Define a GhostScript powered transformer for generating PNGs from PDF files, then override the regular PDF to Image transformer definitions from content-services-context.xml with ones that call GhostScript instead of the com.sun in-JVM one 36026: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36027: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36028: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36029: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36030: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 34921: THOR-1176: GoSquared analytics script 34922: THOR-1176: Removed accidentally committed file 34923: Merged BRANCHES/V4.0 to BRANCHES/DEV/THOR1_SPRINTS 34914: Merged DEV to V4.0 34889: ALF-12678: Errors in log on startup (ts.alfresco.com 4.0) BasicHttpAuthenticatorFactory.BasicHttpAuthenticator.authenticate(RequiredAuthentication, boolean) handles AuthenticationExceptions from authenticateAsGuest() calls. The result is a 401 Unauthorized response. 34920: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/V4.0 34892: Fix for ALF-12930 34917: Fix for ALF-12930 - pushed down to 3.4.X Also related tweak for ALF-10823 to EditionInterceptor now that the repository returns 401 for guest auth unsupported. 36032: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36033: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36034: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 35071: Merged BRANCHES/DEV/V4.0-BUG-FIX to BRANCHES/DEV/THOR1_SPRINTS: 31745: ALF-11268 adding logging.properties and suppressing activiti-logging (which is not log4j but plain JUL) 32336: ALF-11607: "DB2/LINUXX8664" now recognized as DB2 database-type in activiti 32944: ALF-12066 issue with the db2 create-script for activiti (note: pre-reqs for cleaner merge of r34747) 36035: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 35072: THOR-1245: initial fix for doclib test case 36036: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36037: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36038: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 35211: THOR-1182 "MOBREG-07: A page exists confirming the users registration and invites them to enter the password they registered with" 35294: THOR-1309 - F405: Account Types aligned to sales and marketing requirements 35311: Merged BRANCHES/DEV/THOR1_MOBREG to BRANCHES/DEV/THOR1_SPRINTS: 34958: Fix for THOR-1185 and change password hashing to use SHA-2 35026: THOR-1185: "MOBREG-16: New sample ACP for users home site is loaded" - hook into documentLibrary node creation to do the sample acp import 35302: THOR-1185: "MOBREG-16: New sample ACP for users home site is loaded" - use SHA for password hashing - ensure that sample content is added only to home sites 35309: THOR-1185 "MOBREG-16: New sample ACP for users home site is loaded" - minor update to sample content acp 35310: THOR-1185: "MOBREG-16: New sample ACP for users home site is loaded" - removed un-needed properties from repository.properties 35326: Merged BRANCHES/DEV/THOR1_MOBREG to BRANCHES/DEV/THOR1_SPRINTS: 35324: Fix build 35327: Fix build 35337: THOR-1309 - F405: Account Types aligned to sales and marketing requirements 35533: THOR-1309: F405: Account Types aligned to sales and marketing requirements: 35672: THOR-1313 - F410: List accounts filtered by account type 35673: Minor: Update account type ids (as used by tests) to be consistent with the latest config (as per THOR-1309) 35695: THOR-1313 - F410: List accounts filtered by account type 35704: Fix Get Account Types REST API (to also include network admin quota) - related to THOR-1309 / THOR-335 35770: Minor: add additional logging only (THOR-1323) 47183: Fix merge issue 47205: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: - pre-merge of repo parts (note: AD mentioned content transform priority bits will change again when merging to HEAD) 36617: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 36564: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 35780: THOR-1312 - F409: List accounts optionally sorted by (numeric) account type id (ascending or descending) 35807: THOR-1335 - support bulk email address domain validation 35903: THOR-1312 / THOR-1313 -List accounts optionally filtered or sorted 35904: THOR-1310 / THOR-1311 - List accounts optionally sorted by total user account or total file size (or account type, as previously) 35945: Merged BRANCHES/DEV/mward/thor_transforms to BRANCHES/DEV/THOR1_SPRINTS: 35819: Added transformation server changes. 35820: Fixed compilation error, though a size of -1 for getActiveTransformers(...) is not likely to be valid. 35823: Transformations: changed default transformation server URL to be blank. 35897: Remote transformations server: fixed webscript bean id so that it responds to requests correctly. 35898: Remote transformations server: fixed JSON generation/parsing. 35910: Remote transforms server: fixed broken RemoteAlfrescoTransformerTest 35920: Remote transformation server: fixed broken test. 35937: Remote transformation server: integrated HTTPS support via HttpClientFactory. 35939: Remote transformations: added a boolean client-enabling property. 35941: Remote transformations: changed ProxyContentTransformer to delegate isExplicit() calls to worker. 36011: Merged BRANCHES/DEV/THOR1_QUICK_SHARE to BRANCHES/DEV/THOR1_SPRINTS: 34685: Creating Quick Share branch 34826: First cut of THOR-1270 "F387: As the link receiver, I can view the Document Preview in the browser without having to login" 34868: More on THOR-1270 "F387: As the link receiver, I can view the Document Preview in the browser without having to login" 34901: QuickShare REST API - WIP ... note: API will change :-) 34933: QuickShare REST API - WIP 34934: QuickShare REST API - WIP 34941: QuickShare REST API - WIP ... note: API will change :-) 34989: QuickShare REST API - WIP 34995: QuickShare REST API - WIP 34996: QuickShare REST API - WIP 35011: QuickShare/PublicView REST API 35025: F387: As the link receiver, I can view the Document Preview in the browser without having to login 35035: QuickShare/PublicView REST API 35052: QuickShare/PublicView REST API 35069: More on THOR-1270 "F387 As the link receiver, I can view the Document Preview in the browser without having to login" 35094: Removed cpnfig property that was commited by mistake 35111: First cut of THOR-1268 "F378: As a user I can choose to share a document" 35252: THOR-1271 "F388: From the view page, I can easily sign up or login for Alfresco. Sign Up is embedded in the page and Login will redirect you to the Alfresco Login page" partial commit 35254: First cut of THOR-1268 "F378: As a user I can choose to share a document" part 2 35255: THOR-1270 "F387: As the link receiver, I can view the Document Preview in the browser without having to login" 35264: QuickShare/PublicView REST API 35317: THOR-1322: New metadata doesn't return "webpreview" in thumbnails (part 1) 35319: QuickShare REST API - fix tenant ref when share'ing 35330: QuickShare REST API - fix THOR-1322: New metadata doesn't return "webpreview" in thumbnails (part 2) 35368: QuickShare REST API - THOR-1336: (F418) Get context (nodeRef, siteId, tenantDomain) for a shared_id 35376: QuickShare REST API - fix THOR-1273 (unshare document) 35424: THOR-1271 "F388: From the view page, I can easily sign up or login for Alfresco. Sign Up is embedded in the page and Login will redirect you to the Alfresco Login page" partial commit 35425: THOR-1271 "F388: From the view page, I can easily sign up or login for Alfresco. Sign Up is embedded in the page and Login will redirect you to the Alfresco Login page" partial commit 35566: Fixed THOR-1268 "F378: As a user I can choose to share a document" 35617: THOR-1350: Update Slingshot API (doclib2) - make the "qshare:sharedBy" return full details (instead of just userName) 35624: THOR-1339: QuickShare REST API 35682: Fixed THOR-1268, THOR-1339 & THOR-1269 35935: QuickShare REST API - add "system.quickshare.enabled" prop (if set to false then disables QuickShare service/API) 35996: Fixed THOR-1369 & THOR-1270 35997: Fixed THOR-1369 & THOR-1270 part 2 36000: Fixed THOR-1369 & THOR-1270 part 3 36082: THOR-1270 "F387: As the link receiver, I can view the Document Preview in the browser without having to login" - Re-added "Document Details" link public share page 36128: Merged THOR1_SITE_INVITE_THOR-809 to THOR1_SPRINTS THOR-809 "Site Invites Usability - Finish as per original design" - First cut of: #4. Need to be able to cut and paste a long list of emails into the box and have them turned into emails. List can be space, new line or comma separated. THOR-809 "Site Invites Usability - Finish as per original design" - Second cut of #4. Need to be able to cut and paste a long list of emails into the box and have them turned into emails. List can be space, new line or comma separated. * Tooltip is now a common one for all emails rather than individual since that caused issues when being scrolled inside div element. This approach is also less intrusive THOR-809 "Site Invites Usability - Finish as per original design" - Third cut of #4. Need to be able to cut and paste a long list of emails into the box and have them turned into emails. List can be space, new line or comma separated. * Scroll bar is only visible when needed * Now works in IE7 36153: THOR-694: File size upload limit is not adhered to by Alfresco repository 36186: Merged BRANCHES/V4.0 to BRANCHES/DEV/THOR1_SPRINTS: 32842: ALF-11947: Contributor/consumer can't like/unlike or comment document/folder if versionable aspect applied 32850: ALF-11947: Contributor/consumer can't like/unlike or comment document/folder if versionable aspect applied (THOR-1208, THOR-1210) 36188: Merged BRANCHES/DEV/V4.0-BUG-FIX to BRANCHES/DEV/THOR1_SPRINTS: 34193: ALF-12205 - Contributor can't comment document/folder if versinable aspect is applied (THOR-1209, THOR-1302, THOR-1387) 36237: THOR-28: Account Service - Get Account Method API 36252: THOR-28: Account Service - Get Account Method API 36624: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 36565: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36289: THOR-1314: Changed e-mail action in upgrade button to link 36290: MT fix for SiteService 36291: THOR-538 / THOR-1168 - auto remove external user from a secondary network when they no longer belong to any sites ... 36292: THOR-900: Modified URL rewrite filters to enforce encoding of "@" symbols sin URL so that the invalid session timeout error doesn't occur 36631: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 36570: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36308: Merged BRANCHES/DEV/V4.0-BUG-FIX to BRANCHES/DEV/THOR1_SPRINTS: 36109: ALF-7874 MimeType definitions for Adobe AfterEffects files 36110: ALF-7874 Upgrade Tika for improved detection of Adobe Premier and AfterEffects 36112: ALF-7874 MimeType definition addition for Adobe Premier files 36313: Merged BRANCHES/DEV/V4.0-BUG-FIX to BRANCHES/DEV/THOR1_SPRINTS: 33730: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.0-BUG-FIX: 33726: ALF-7264 Upgrade Commons Validator to 1.4.0 Final, fixing VALIDATOR-292. Then, update the EmailValidator flags to allow local addresses during validation, so that Alfresco can send emails to @localhost addresses without error 33779: Upgrade Tika for ALF-12714 33782: ALF-12714 Add 3GPP/3GPP2 video, and MP4 Audio mimetypes 33783: Update Tika for more MP4/QuickTime support, and enable MP4 audio metadata extraction + "quick" testing 34884: Merged HEAD to BRANCHES/DEV/V4.0-BUG-FIX: 34883: Tika and POI upgrade for ALF-13106 36340: AttributeService improvement: update puc value + cache (based on keys rather than id) 36341: Tenant usage quotas improvement 36412: THOR-1403: added connection timeout property to HttpClientFactory. 36413: THOR-1403: added 3s connection timeout to transformer client configuration. 36425: Tenant usage quotas improvement 36428: Merged THOR1_SITE_INVITE_THOR-809 to THOR1_SPRINTS THOR-809 "Site Invites Usability - Finish as per original design" - Added semicolon as email separator - Fixed seecltion bug for SF, text now selected on first click and deselected on 2nd click (tested IE, SF & Chrome) 36431: Resolve THOR-1406: Emails with apostrophes aren't accepted 36455: Resolve THOR-1146: qamy.alfresco.com: incorrect URL for the RSS items 36456: Resolve THOR-1061 Incorrect text for the 'Task History' link 36457: Resolve THOR-1345: Missing apostrophe on mobile / cloud sign-up web page 36459: Merged BRANCHES/DEV/mward/thor_transforms to BRANCHES/DEV/THOR1_SPRINTS: 36458: THOR-1411: New Alfresco remote transformation subsystem is DEBUG logging by default 36460: Resolve THOR-1294: Terms and Conditions Link in Sign up screen does not work 36464: Resolve THOR-1238: Missing message string: error.header.wrong-reset-user 36472: Merged DEV/THOR1_QUICK_SHARE to DEV/THOR1_SPRINTS Improvements for THOR-1270 "F387: As the link receiver, I can view the Document Preview in the browser without having to login" - Added new component evaluator for bringing in different components based on page id - The title of the quickshare page now contains the document's name (to improve the social "Share with:" experience) - Added new icon for page not found screen 36477: AttributeService improvement: fix test/build (PropertyValueDAOTest.testPropertyUniqueContextValue) 36482: Resolve THOR-1416: Remove Polymorph Client 36484: Merged BRANCHES/DEV/V4.0-BUG-FIX to BRANCHES/DEV/THOR1_SPRINTS: 36259: ALF-13933 Alfresco needs to be able to support LibreOffice for transformations << Developed on Windows 7. Might need more work on Linux to get LibreOffice to shut down, but should be okay with OpenOffice 3.2 which was used in the previous release. >> - Updated jodconverter to latest version jodconverter-core-3.0-SNAPSHOT-patched.jar 28/4/2012 which is newer than 3.0 beta-4 - Applied patch for http://code.google.com/p/jodconverter/issues/detail?id=103 to handle setting the env for LibreOffice 3.5 - Modified code to use partial GNU style options (not used for -env!) when using LibreOffice - Added OpenOfficeCommandLine to dynamically supply OpenOffice or LibreOffice command line args for OOoDirect - Tested to work with OpenOffice 3.4 and 3.2 on Windows 7 36264: ALF-13933 Alfresco needs to be able to support LibreOffice for transformations - remove old jodconverter-core-3.0-beta-3.diff 36273: ALF-13933 Alfresco needs to be able to support LibreOffice for transformations - Return a dummy OpenOffice command even when there is no OpenOffice/LibreOffice installed or on the path. 36485: Resolve THOR-1418: Switch off S3 encryption by default 36508: THOR-1200 (CORE CHANGE): Modification to forms-runtime to prevent field undefined errors 36509: THOR-1200 (MODULE CHANGE): Re-introduce folder rules 36517: THOR-1353: Ensure IE8 users can create tasks 36521: Resolve THOR-1348: Account Settings screen should display Account Type 36530: Fix solrcore.properties 36540: THOR-1200: Force folder rules to run asynchronously 36634: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 36633: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 36601: Merge from THOR1_QUICK_SHARE to THOR1_SPRINTS 36599: Improvements for THOR-1270 "F387: As the link receiver, I can view the Document Preview in the browser without having to login" - Made the "Preparing previewer... text get rendered using javascript so Google+ won't include it in its description when sharing quickshare links 47278: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1: 37509: Merged BRANCHES/DEV/THOR1_SPRINTS to BRANCHES/DEV/THOR1: 37167: Merged HEAD to BRANCHES/DEV/THOR1_SPRINTS: 37157: THOR-1459: WebDAV: site names cannot start with 'webdav' 37302: THOR-1403: ensure indexing happens asynchronously on upload. 37303: THOR-1403: removed thumbnail creation during file upload. 37459: THOR-1429: Webdav returns 500 when you don't have access to moderated site 37469: THOR-1475: Fixed avatar thumbnails for users joining/leaving/role change on sites. 37506: Resolve THOR-1481: Cannot preview PDFs over 3.5MB in size 47279: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: 39341: Resolve CLOUD-181: Webdav Cluster Failiure in Cloud1 build 148 startup error: java.lang.ArrayIndexOutOfBoundsException: 72 47281: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: 39314: Resolve CLOUD-203: WebDAV incorrectly handles file / directory names with spaces in them (also fix compile error introduced from earlier merges - partial changes sourced from CLOUD2) 47359: Fix merge/test error (WebDAVMethodTest.canGetStatusForAccessDeniedException) 47384: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: Merged DEV/CLOUD_SIGN_UP into DEV/CLOUD1 (cloud priority 47389: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: Merged DEV/CLOUD1-BUG-FIX into DEV/CLOUD1: 41674: ALF-15967: Using START_USER_ID_ instead of "initiator" property to query process instances started by user X to prevent extra joins + removed unused constants 41650: Fixed CLOUD-667: Merged fix for ALF-14438 into CLOUD1-BUG-FIX + using START_USER_ID_ instead of custom "initiator" property to query initiator to boost performance even more 47431: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: CLOUD-808: Fix for timer deploying MT-process when shared is required caused test to fail 47516: Merged BRANCHES/DEV/CLOUD2 to BRANCHES/DEV/CONV_V413: r44571 | dcaruana | 2012-12-10 17:55:00 +0000 (Mon, 10 Dec 2012) | 1125 lines Merged BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN to BRANCHES/DEV/CLOUD1: 44527: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 38425: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 33822: Merged BRANCHES/DEV/V4.0-BUG-FIX to HEAD: 33813: ALF-12736: Stack specific: 'Getting updates failed' notification appears when non-admin user creats DWS 33821: ALF-12212 - SPP/Vti requires that all links include a protocol and are valid, while share does not. When building the Vti response, prefix URLs with http:// if they lack a protocol at the start, and ignore any that are malformed 38431: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 33893: SharePoint refactoring - first part of removing the old Alfresco v2 code 38432: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 33905: SharePoint Refactoring - Move to only a single class for the VersionServiceHandler 38433: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 33906: SharePoint Refactoring - Move to only a single class for the User and Groups Handler 38434: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 33907: SharePoint refactoring - fix deprecation warnings by switching to the new way of finding people, when searching for a user with a given email address 38435: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 33923: SharePoint refactoring - Start on merging the old style and new style List support, mark the old style as deprecated, and add TODOs where needed for future changes 38440: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 35486: ALF-13028: Sharepoint broken by changes to WebDAV 38442: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 35530: ALF-13028, ALF-13696: broken sharepoint module 38443: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 36148: Merged BRANCHES/DEV/BELARUS/HEAD-2012_04_25 to HEAD: 35710: ALF-13028 : Sharepoint broken by changes to WebDAV 38444: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37648: Sharepoint: fixed typo in class name VtiDocumentHepler 38447: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37929: First try at incremental-spp-tomcat-exploded target 38451: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37943: Sharepoint: fix build for compile-spp dependency and for new incremental-spp-tomcat-exploded target. 38453: Sharepoint: remove error causing property injection - probably came from a double-merge. 38472: Sharepoint: fix missing singletonCache property for VtiPathHelper bean, probably lost in merges. 38477: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37926: Correct handling of GetMeetingsInfo - lcid may not always be set 38478: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37927: More parameters that are optional 38481: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37928: More parameters that are optional 38483: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37931: A site may not have a calendar container yet 38485: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37933: Set the response type to text/xml as required 38486: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37938: Stub out support for UpdateMeeting and SetWorkspaceTitle 38487: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37939: Fix title vs name confusion in meeting sites, support setWorkspaceTitle, and correct the error reporting on create site 38488: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37940: SPP Vti SOAP error codes must be zero padded to 8 characters 38489: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 37941: Improve error handling in create/set title/delete, and match the required error codes 38491: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38005: Correct logic to fix test 38493: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38006: DTEND is optional in iCal, if not set it means the meeting ends at the start time 38494: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38007: Pull out commong workspace related code to a parent endpoint class 38496: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38009: Sharepoint [MS-VERSS]: GetVersions should state whether file has versioning enabled. 38498: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38011: More refactoring to use the new commong workspace parent endpoint 38501: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38012: Start to clean up how exceptions are done in the meeting handler layer 38013: Start to refactor the common parts of meetings to a parent abstract class, and stub out the SetAttendeeResponse endpoint 38014: Fix import warnings 38015: More towards Add/Update/Restore meeting 38016: Common building of the MeetingBean 38017: Improve error reporting on add/update meeting, to match the spec requirements 38018: Sharepoint [MS-DWSS]: CanCreateDwsUrl returns GUID if requested name is empty string. 38019: ALF-14687 - ISO8601 dates from non-Alfresco sources may have more than 3 millisecond digits 38020: Stub out the SetAttendeeResponse endpoint, but the handler does nothing with the details 38021: More meeting exception improvements 38503: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38077: Refactor the Meeting related endpoints to use common superclasses, and fix some common problems / exception handling in the process 38078: Further error handling checks 38504: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38080: Match the rather strange behaviour of getMeetingsWorkspaces and the recurring flag, as set down in the spec 38505: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38088: Sharepoint [MS-DWSS]: partial fix to XML fault message generation. 38089: Sharepoint [MS-DWSS]: fuerther partial fix to error message generation for CreateDws. 38506: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38091: Sharepoint [MS-DWSS]: S1_TC08: returning error with AlreadyExists but should be ServerFailure. 38507: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38093: Support filtering by boolean properties, as well as String ones, in GetChildrenCannedQuery 38094: Support for listing calendar entries by their outlook status (isOutlook / OutlookUID), via a Canned Query 38095: De-Lucene the SharePoint / Vti Meeting Service handler, using the new CalendarService list method 38096: Sharepoint [MS-DWSS]: S1_TC09 and others, hyphens in GUID were being translated to underscores. 38097: Vti/SPP support for restoring a deleted meeting (assuming it is still available in the archive store) 38508: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38099: Make the error message a bit more helpful, in the case that the assoc qname is too long 38100: Check for errors from the create site share call 38101: Validate that the site Short Name isn't too long during creation, and throw a helpful exception if it is. (Site Short Names get used in Authority Names, which are limited by the DB model to 100 characters) 38102: Update the site name max length check - need to account for the role group names too 38103: There is a maximum size of a site short name, based on the limits of the authority names in the permissions service, so truncate the Vti/SPP site name before creation to fit within that 38104: Specific error response needed for GetMeetingsInformation on a subsite 38509: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38134: Sharepoint [MS-VERSS]: S1_TC05 removed manual entity encoding of quotes, as dom4j was then escaping the ampersands. 38510: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38139: Sharepoint [MS-DWSS]: fixed error message generation for CreateFolder. 38140: Sharepoint [MS-DWSS]: Result element should be standalone XML. 38141: Start removing deprecated, old style lists in favour of the full list support 38511: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38143: Improve handling of abstract lists, and convert more classes to use that method 38144: More convertions to AbstractListEndpoint, and improve logging by centralising it better 38512: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38146: Prepare to do more of the List details not via hard coded FTL 38147: Convert to only having the fields hard coded in the ftl, and the rest dynamically generated 38148: Remove deprecated old style ListBean (now all replaced with ListInfoBean), and tidy up around the removal 38514: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38156: Start on processing the request for UpdateListItems, and support GetList by UID as well as Name 38157: Sharepoint [MS-WEBSS]: fixed GetWeb to respond with the "context site" if "." or "" requested in SOAP body. 38515: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38229: Improve the list definition model, and add more support for UpdateListItems 38516: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38231: Refactor the current (rather hard coded) GetListItems support into an abstract class, and then use that to stub out the other two GetListItems* endpoints 38232: Sharepoint [MS-WDVMODUU]: implemented WebDAV PUT method. 38233: Stub out Change Since support 38517: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38237: Improve error handling, and reduce some duplication 38238: More details needed 38239: Use the same rendering of a list details 38518: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38381: ALF-14835: Sharepoint/WebDAV: exclusive lock fails deemed to be owned by someone else. 38382: Sharepoint [MS-WDVMODUU]: activity posting disabled upon DELETE. 38785: Merged alfresco/HEAD to alfresco/BRANCHES/DEV/CLOUD1_SP: 38752: WebDAV/Sharepoint: extracted WebDAVHelper to spring configuration. 44543: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 38996: WebDAV: added getUrlPathPrefix tests for WebDAVHelper. 38997: WebDAV: added setter for urlPathPrefix property (to aid in spring config), ensures path prefix terminated in slash. 39001: WebDAV: getURLForPath(...) appends trailing slash to path prefix if missing. 39002: WebDAV: removed redundant createDAVHelper() method. 39016: WebDAV: added getRepositoryPath(request) method (replaces WebDAV's static method) 39017: WebDAV: replaced use of static WebDAV.getRepositoryPath(req) with non-static version in WebDAVHelper. 39019: Cloud/SPP: thor module must have dependency on SPP module. 39020: Cloud WebDAV: CloudWebDAVServlet no longer uses static getRepositoryPath(req) method. 39021: Cloud WebDAV: override getRepositoryPath(req) with version that gets path within tenant. 39023: SPP core: added hook to VtiWebDavAction to allow overriding of execution behaviour. 39025: SPP: minor logging improvement. 39026: SPP: define SPP version of WebDAV helper and inject into VtiWebDavAction instances. 39060: Cloud SPP: introduced cloud/spp webdav helper. 39061: Cloud SPP: missing part of commit, moved methods out of CloudWebDAVHelper and into SPPCloudWebDAVHelper. 39095: Cloud WebDAV: move doclib URL munging out of core WebDAV. 39182: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 39167: WebDAV: remove static replace(str, repl, with) method. 39185: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 39172: WebDAV: removed static WebDAV.getRepositoryPath() method. Only use the non-static version. 39186: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 39173: WebDAV: fix broken code from moving inlining of getRepositoryPath(...) from WebDAV to WebDAVHelper. 39187: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 39174: WebDAV: removed redundant DIR_SEPARATOR constant. 39189: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 39178: WebDAV/SPP: remove static WebDAV.getURLForPath(...) methods. 39323: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 39203: WebDAV: fix broken UnlockMethodTest. 39324: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 39204: WebDAV: fix broken WebDAVMethodTest. 39325: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 39309: Sharepoint: no longer uses sysAdminParams to determine incoming URL path prefix. 39330: Sharepoint: fix code broken by merge -c 39325 (from HEAD). 39346: Cloud/sharepoint: fix for merge from HEAD, -c 39309 (remove sysAdminParams as supplier of URL path prefix) 39365: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 39364: Sharepoint: making PROPFIND more tolerant of null values in a node's properties. 39474: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 39473: WebDAV: introduce getChildren() method for helper. 44544: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 39541: Sharepoint: added test for decomposeDocumentURL functionality. 39552: Sharepoint: added doDecomposeURLWork test for incorrect URI path prefix. 39570: Sharepoint: refactoring for cloud overrides and testing improvements. 39674: SPP core: use VtiPathHelper.getPathForURL() instead of using request directly. 39676: SPP core: refactored action execution to provide hook for subclasses. 39695: SPP: null GUID no longer results in an empty rid{} 39696: SPP core: VtiWebDavAction - setting the method's details is delegated to the DAV executor. 39698: SPP core: added hook for detecting whether a URI path does not specify a site. 39701: SPP core: abstract parent for vtiRequestDispatcher makes it possible to override bean and inherit configuration. 39702: SPP core: move support methods from test class into abstract base class. 39705: SPP cloud: added cloud specific filter and path helpers. 39707: SPP cloud: remove redundant code from CloudwebDavExecutorImpl 39708: SPP cloud: introduce request handling logic in common for WebDAV and SPP. 39709: Cloud SPP/webdav: WebDAV uses common request handling class. 39710: SPP cloud: SPPCloudWebDAVHelper was incorrectly stripping off tenant domain for getRepositoryPath(). 39712: SPP cloud: introduce a cloud-specific VtiRequestDispatcher. 39723: SPP core: servlet filter now registered for forwards as well as fresh requests. 39724: SPP core: removed URL decoding of URL in XML response generation. 39759: SPP core: use separate servlet mappings for /alfresco/* and /* 39865: Cloud SPP: cleaned up runAsTenant usage. 39868: SPP cloud: missing files from previous commit ("cleaned up runAsTenant usage") 39916: CLOUD-28: cloud SPP raises activity posts for PUT, DELETE. 39930: Cloud SPP: tenant domain is extract from RPC method request with "url" parameter, instead of through request URL path. 39941: Cloud SPP: reintroduced separate instances of CommonRequestHandling that was lost by moving instantiation into spring. 39971: CLOUD-137: 4.4 Add Mixpanel Analytics events to SharePoint Protocol 39972: CLOUD-137: 4.4 Added an "spp" event source. 44545: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 40006: Bamboo/CI: added dependency on spp module when building cloud. 40007: WebDAV - cloud only: added missing WebDAVHelper constructor argument to spring definition. 40015: Cloud SPP: bake SPP module into WAR file at build time. 40027: Cloud SPP: Committing binaries that can be used by ops for testing/prototyping. 40365: WebDAV/SPP: moved webDAVLockService bean definition into webdav-context.xml 40366: WebDAV/SPP: moved webDAVLockService to remote-api-context.xml 40368: SPP: removed circular task dependency. 40373: Cloud SPP: added incremental-spp-tomcat-exploded as dependency of incremental-cloud-repo-exploded 40376: Test to see if this fixes build failures, by adding SPP classes/config to unit testing classpath. 40380: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 38861: WebDAV: fixed dependency problem; fixes test RepositoryStartStopTest. 40384: Cloud SPP: ensure jetty libs on classpath during test. 40392: Cloud SPP: remote-api-context.xml must be loaded before cloud and SPP modules. 40400: Cloud SPP: fix classpath to use fileset instead of pathelement. 40444: SPP build problems: attempt to ensure thor config comes after SPP config in unit testing classpath 40447: Cloud SPP: ensure SPP module is loaded prior to cloud module. 40450: SPP Cloud: backing out change 40444 (CI build changes) 40451: SPP Cloud: another attempt to straighten out testing classpath problems. 40456: Temporarily disabling explicit SPP module import. 40483: Cloud SPP: fixed broken test and clarified behaviour in comments. 40519: Cloud SPP: remove exclusion of module.properties when copying resources during deploy-spp-tomcat-exploded ant target. 40523: CLOUD-25: 3.1 The protocol must be secured using the user's standard Cloud credentials 44546: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 40551: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 38401: Fix activities feed mail notifications. 38614: Resolve CLOUD-157: Cloud1 does not deploy due to addDutchEmailTemplates patch failing 38676: Exclude irrelevant tests even if minimal.testing? flag is not set 38726: Resolve CLOUD-160: "Data too long for column 'report' at row 1" when deploying Cloud1-144 to Test 38734: Fix CLOUD1 build to ensure test results from previous builds are cleaned 38754: Fix test 38765: Merged BRANCHES/V4.0 to BRANCHES/DEV/CLOUD1: 37766: Fix for ALF-14529 Share Solr admin console page shows "Unavailable: 05120041... 37800: ALF-14145 Flash preview quality is poor - Reverting change r30483 V3.4-BUG-FIX Sept 13 2011 for ALF-9417 swf.encoder.params=-s zoom=72 -s ppmsubpixels=1 -s poly2bitmap=1 -s bitmapfonts=1 being changed back to: swf.encoder.params=-s poly2bitmap,subpixels=72 The original 001-Cover_Cover.pdf file from ALF-9417 does now preview in 4.0.2 39058: CLOUD-190: Ensure that sub-folders of folders named "res" and "proxy" can accessed. 39206: Fixed CLOUD-198 "WASA - XSS issue with quickshare" 39210: Cloud1 build fix 39236: Fixed and baked in Share Hazelcast Cloud specific configuration into thor-share module. Now only need to override hz:hazelcast bean with AWS key specifics for test/production. 39314: Resolve CLOUD-203: WebDAV incorrectly handles file / directory names with spaces in them 39341: Resolve CLOUD-181: Webdav Cluster Failiure in Cloud1 build 148 startup error: java.lang.ArrayIndexOutOfBoundsException: 72 39357: CLOUD-164: Remove categories from document library 39358: Resolve CLOUD-186 500 error displayed when trying to add 'Content I'm editing' dashlet 39388: Resolve CLOUD-185: My / Site Activities displayed incorrectly 39391: Resolve CLOUD-161 Patching is taking too long - specifically patch.fixBpmPackages 39499: Resolve CLOUD-395 TEST server startup log - upgrading to cloud1 39500: Resolve CLOUD-187 java.lang.IllegalArgumentException: Must provide existing lockToken 39554: BDE-90: Setup build for deployment of Cloud artifacts to Maven repository 39559: Merged THOR1 to CLOUD1 38529: CLOUD-399 / ALF-14887: User sign-up issue - Workflow prior to mobile registration did not have the isPreRegistered property. Changed it so that if the property does not exist, it defaults to false. - Logged warning with email address in log for easier location of error. 39553: CLOUD-400 / ALF-15149: Prevent an infinite loop in AlfrescoSolrEventListener.RemoveNullEntriesCacheMatch.updateCache() 39563: Merge BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to DEV/CLOUD1 (r38673-39562) 39567: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 39566: Allow the sync mode to be fetched by guest (it need not be hidden), then fetch from Share as guest, to avoid problems when fetching the sync mode if the current user's credentials have expired (otherwise it breaks the redirect back to the login) 39568: Fix YUI options to match the version in use (-p is no longer supported) 39569: Cloud is always build on top of enterprise, so remove the duplicated projects.xml taken from enterprise and use the enterprise projects.xml instead. (Fixes warnings during build) 39575: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 39574: Cloud lacks a guest mode, so switch to unauthenticated for the sync mode fetch (user credentials may not be available) 39579: Undoing some CloudSync-specific restrictions on what test cases are run. 39584: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 39580: CloudSync: fix Unsync - to ensure we don't leave sync aspects (such as failed, which reappears if src file is re-synced) 39586: Merged V4.1 to CLOUD1 (might fix the build) 39207: ALF-15015 follow-up: make sure the first enterprise test that runs has no specific license, since we just reset the database 39588: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 39587: ALF-15222: Cloud Folder picker: Newly created folder isn't selected 39596: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 39589: ALF-15098: Folder Picker causes JS error in the document details page 39561: Mark from Thor1 to V4.1 to Cloud1 39602: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 39076: ALF-15013 ALF-15015: Reset database before running enterprise tests, otherwise SecurityTestSuite leaves it in a bad state 39608: Removed incorrect com.sun import 39610: Merged HEAD to BRANCHES/DEV/CLOUD1 38834: Merged BRANCHES/DEV/V4.1-BUG-FIX to HEAD 38607: Fix for ALF-14934 - Components YUI menus hit area is small. iPad specific CSS fix. 38631: ALF-14820 - Document Library - Upload actions and File Browse buttons (doclib, user profile image, upload vers ion etc) - not appropriate for iOS. 38632: ALF-14934 - increased hit area for filter and tree/category nodes for ios. 38667: Repository toolbar changes for ALF-14820 38668: Patched YUI menus to support additional touch events (touchend specifically) and as custom menu events to fix ALF-14809. 38738: Correct fix for ALF-13650: Double byte code white spaces in front and at the end of a file name should be removed as the same behavior of Single byte code white space. Altered Alfresco.util.trim() in order to support double byte code white spaces. - did not handle null/defined which are returned when creating content in Wiki, Blog and Discussions. 38745: ALF-14807 - Fixes related to long file name display margins for iPad on Document Details page. 38790: ALF-14927: Ensure that RSS feed URLs are maintained in RSS dashlet when the URL contains request parameters 38792: ALF-13297 - removed install WebScript webscripts (updated Surf libs - r1094) 38831: Fix for ALF-14992 - Share: display list of rules for a folder: unterminated string literal - actually a wider spread issue of failing to handle multiple line field values (e.g. "description" fields) wh en rendering json into a component page or template 39067: Fixed drag n drop issues for m.share on ipad - Added ootb support for touch events to drag n drop classes in yui-patch.js - ALF-14816 Dashboard - Cannot customize dashboard layout - ALF-14817 Customize Site - Can't add or change the order of page components - ALF-14822 Rules - Cannot reorder rules when multiple rules are defined on a folder - This also means all "dividers" on the documentlibrary, discussions etc pages now work 39614: Reinstate repository tests (so far, only RepositoryStartupTest is run) 39615: BDE-90: do not try to deploy to Maven repo artifacts which are not built 39642: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 39597: CloudSync: tweak a couple of unit tests 39598: CloudSync: ALF-14655 - update simple file sync (to test multiple sync'd files) 39606: CloudSync: minor log level adjustments 39607: CloudSync: ALF-14483 - SyncTracker - enable sync sets to be pushed in parallel 39613: Refactor of test code. SyncTrackerComponentTest now uses JUnit Rules to manage temporary content and to undo mocking out of services. 39616: CloudSync: CloudSync: ALF-14483 - SyncTracker - multiple syncs (to different tgt folders) with multiple files 39633: CloudSync: ALF-14483 - SyncTracker - enable sync sets to be processed in parallel 39639: ALF-15238: Bitrock Installer: Readme.txt should be updated - Alfresco version is 4.0 39640: ALF-15231 Restore, with refactorings / making more general, the ability to lazy create the remote credentials container if bootstrap has not created it (eg on Cloud) 39641: ALF-15230 Refactor the lazy container creation code, to use the new SystemNodeUtils helper 39644: ALF-15230/ALF-15231 Disable two patches on cloud, which create new system containers, as the number of tenants means this is a large amount of work. Services will not lazy create if needed, but for on-premise we retain the up-front creation to avoid surprises and allow predictable permissions in advance if required 39645: Disabling one test on CLOUD1, pending discussion with Mark, Jan. 39661: Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/CLOUD1 39660: Fix for ALF-14244 - Sort options are not appropriate for some pre-canned Document Library queries and need disabling in the UI as they can confuse the user. Implemented by Valery Shikunets. 39666: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: 39665: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39664: CloudSync: ALF-14483 - SyncTracker unit tests - simple file sync test 39675: Resolve CLOUD-410: Disable multi-threaded SOLR 39680: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 39643: ALF-15220: Indirect sync and sync failed indicators appeared together on nodes during the demo 39653: ALF-15041: Cloud Sync doesn't work with IE7 39670: Rework to cloud key license stuff - introduces a ValidLicenseEvent 39673: New unit test for adding checked out nodes into a sync set. 39677: ALF-14903 Move the Sync allowed/not-allowed check, based on Network/Tenant, to AbstractCloudSyncAbstractWebScript, and have this used by the webscripts based on this too. Then, allow UnSync to proceed no matter what the tenant status, and add unit tests 39699: ALF-14454: get rid of hrtlib.jar 39700: BDE-90: deploy cloud-specific artifacts 39731: CLOUD-421: Merged PATCHES/V4.0.2 to CLOUD1 39725: ALF-15176: Multi-threaded SOLR tracking suddenly stops leaving transactions unindexed - Debugged on environment provided by Antonio - If the last incomplete batch in a chunk consists entirely of empty transactions (or transactions consisting entirely of nodes updated in later transactions) it would loop forever 39738: CloudSync: add default (info) Sync log level 39744: Merged HEAD to BRANCHES/DEV/CLOUD1 (for BDE-73): 39282: Get context from classpath rather than file system 39409: fetch application context from classpath rather than file system 39433: tune AVM tests to make them resilient to a change of working folder 39743: Allow to override the location of test resources using a system property 39745: BDE-73: Add experimental pom.xml files 39755: BDE-73: Add experimental pom.xml files 39830: Resolve CLOUD-423: Concurrent registration / account activations are not handled gracefully 39855: BDE-73 Configure Sonar properly + filter a few failing tests 39877: CLOUD-438: Merged V3.4-BUG-FIX to CLOUD1 36446: Fix for ALF-13404 - Performance: 'Content I'm Editing' dashlet is slow to render when there is lots of data/sites - Effectively removed all PATH based queries using the pattern /companyhome/sites/*/container//* as they are a non-optimized case - Replaced the "all sites" doclist query using the above pattern with /companyhome/sites//* plus post query resultset processing based on documentLibrary container matching regex - Optimized favorite document query to remove need for a PATH - Optimized Content I'm Editing discussion PATH query to use /*/* instead of /*//* - Fixed issue where Content I'm Editing discussion results would not always show the root topics that a user has edited - Added some addition doclist.get.js query scriptlogger debugging output 36449: ALF-13404 - Fix for issue where favoriates for all sites would be shown in each site document library in the My Favorites filter. 37190: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX 37189: ALF-13404: Performance: 'Content I'm Editing' dashlet is slow to render when there is lots of data/sites - Additional query improvement by Pavel 37835: Fix for ALF-14429 - Recently Modified dashlet takes up to 30 seconds to load after upgrade to Alfresco 3.4.6.23 39878: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1 39775: ALF-15271: "Site Content" dashlet shows all documents from all the sites in Alfresco Share 39919: Added additional configurable datasource that is used by activiti IdGenerator to fetch new block of ID's. Can be defined using JNDI-lookup, reverts to default datasource based on well-know alfresco-properties. 39925: CLOUD-422: Reverted revision 39919, will be re-comitted after CLOUD1 released 39954: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: 39655: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39490: Changing the icon for creating a new folder in the cloud 39580: CloudSync: fix Unsync - to ensure we don't leave sync aspects (such as failed, which reappears if src file is re-synced) 39587: ALF-15222: Cloud Folder picker: Newly created folder isn't selected 39589: ALF-15098: Folder Picker causes JS error in the document details page 39597: CloudSync: tweak a couple of unit tests 39598: CloudSync: ALF-14655 - update simple file sync (to test multiple sync'd files) 39606: CloudSync: minor log level adjustments 39607: CloudSync: ALF-14483 - SyncTracker - enable sync sets to be pushed in parallel 39613: Refactor of test code. SyncTrackerComponentTest now uses JUnit Rules to manage temporary content and to undo mocking out of services. 39616: CloudSync: CloudSync: ALF-14483 - SyncTracker - multiple syncs (to different tgt folders) with multiple files 39633: CloudSync: ALF-14483 - SyncTracker - enable sync sets to be processed in parallel 39955: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: 39656: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39640: ALF-15231 Restore, with refactorings / making more general, the ability to lazy create the remote credentials container if bootstrap has not created it (eg on Cloud) 39641: ALF-15230 Refactor the lazy container creation code, to use the new SystemNodeUtils helper 39643: ALF-15220: Indirect sync and sync failed indicators appeared together on nodes during the demo 39653: ALF-15041: Cloud Sync doesn't work with IE7 39956: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: 39736: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39735: CloudSync: ALF-14483 - multi-threaded SyncTracker 39866: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39670: Rework to cloud key license stuff - introduces a ValidLicenseEvent 39673: New unit test for adding checked out nodes into a sync set. 39677: ALF-14903 Move the Sync allowed/not-allowed check, based on Network/Tenant, to AbstractCloudSyncAbstractWebScript, and have this used by the webscripts based on this too. Then, allow UnSync to proceed no matter what the tenant status, and add unit tests 39683: ALF-15162 - Unclear error message when delta syncing and the target is gone 39686: Replaced GPL license with enterprise licence. 39687: Enterprise license for enterprise remote api project 39867: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39747: ALF-14377 Need to decide what to do when SSD creator user is no longer valid - part 1 nodes are marked. 39748: CloudSync: fix compile error 39767: CloudSync: fix ALF-15145 - Unsync of a folder (sync) that has a working copy file causes indirect icon to remain ... 39775: ALF-15271: "Site Content" dashlet shows all documents from all the sites in Alfresco Share 39810: ALF-15221: Cloud Sync Status: Failure message when getting location is misleading 39812: UI Bug Fixes: - ALF-15174: Uses already copied slingshot.properties file for message strings needed in both repo and Share - Cloud delete file message shouldn't refer to the cloud 39820: ALF-15267: Edit online is available for locked on-premise copy 39826: ALF-14377 - Need to decide what to do when SSD creator user is no longer valid 39870: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39847: ALF-15185: YUI Overlay Mozilla Scrollbar fix should be prevented in recent versions of Firefox too. 39859: ALF-15249 Add a new method hasSyncSetDefintions, with unit test, and use this to skip the Sync Pull step on-premise if no SSDs have been defined on the local system 39860: ALF-14908: Update Sync status dialogue to include the local root folder for indirectly synced nodes. 39914: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39863: ALF-15268: Cancel button disabled in the target selection window for new folder creation in Cloud 39864: ALF-15003 Sync failed for the file (where write access on source removed for sync owner) but no matter this file appears in Cloud 39876: ALF-15301: IE8 specific: It is impossible creating a new folder in a cloud target selection window/Cloud location 39883: CloudSync: fix ALF-15270 - moving directly synced node (on source) causes target node to be deleted 39902: ALF-15041: Cloud Sync doesn't work with IE7 39903: ALF-15041: Cloud Sync doesn't work with IE7 39904: ALF-15041: Cloud Sync doesn't work with IE7 39920: Bug fixing for CSS issues 39959: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: 39951: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39915: ALF-14908: Update Sync status dialogue to include the local root folder for indirectly synced nodes 39924: CSS fixes for the sync panel 39942: ALF-14911 - Path shown in the version comments does not match the path shown in the sync status dialogue - first part share paths are now handled separatly 39943: CloudSync: fix remainder of ALF-15130 - sync time for initial sync of working copy (+ unit test) 39952: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 38871: (RECORD ONLY) Adds edition check before calling enterprise only webscript. Sets sync to OFF for non enterprise editions. 38956: (RECORD ONLY) Chinese Translation for 4.1 39011: (RECORD ONLY) ALF-14225: Refactor Welcome Dashlet to show 4 columns, the 4th one of which is always the cloud sign up text. 39024: (RECORD ONLY) ALF-14225: Adds tracking to URL. 39958: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39957: CloudSync: minor - some info logging (applies to src + tgt) 39967: CLOUD-165: Merged 4.1-BUG-FIX to CLOUD1 39962: ALF-15158: Downloading Office 97 files with .doc extension result in file with .dot extension when using Google Chrome - Added more complete and intelligent content disposition header with the aim of supporting RFC 5987 39977: CLOUD-454 - merge asynchronous dashlet improvements and remove inappropriate sections. 40048: Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/CLOUD1 40022: Fix for ALF-15144 - Slow Share doclib high-level folder browsing due to version history retrieval (CLOUD-467) 40047: Fix from Mike Hatfield for ALF-15372 - Chrome is blocking google map display for geo tagged pictures (CLOUD-468) 40078: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: note: "blending" cloud sync fixes into CLOUD1 (as requested by BR) 39997: ALF-14377: Need to decide what to do when SSD creator user is no longer valid 40013: "Cloud Sync" link should not be shown in "My Profile" in the Cloud 40056: Merged the sync specific changes to the other presentation.css files 40070: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: (record-only: NB - already manually merged) 39566: Allow the sync mode to be fetched by guest (it need not be hidden) ... 39574: Cloud lacks a guest mode, so switch to unauthenticated for the sync mode fetch (user credentials may not be available) 40071: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 39996: ALF-14377: Need to decide what to do when SSD creator user is no longer valid 40010: Enhancement to TemporaryNodes JUnit @Rule so that it gracefully handles checked-out nodes during test code. 40017: During work for ALF-15296, I've fixed up the error handling during sync set creation. 40019: Having fixed the TemporaryNodes @Rule in rev 40010 so that it handles checked-out test files gracefully, I'm removing Jan's workaround for a checked-out test file. 40036: Addressing the important question raised in ALF-14950. 40054: CloudSync: add simple *folder* sync unit test ! 40065: Removal of potential NullPointerException (not observed). 40067: CloudSync: ALF-15287 - folder sync to a clashing name hierarchy causes file to be indirectly synced to the wrong place 40068: Java doc corrections. 40069: ALF-15364: CloudSync: folder sync with lock on-premise - pulled new file is not auto-locked 40104: Fix for CLOUD-476 - userStatusRelativeTime is still referenced in org/alfresco/components/dashlets/my-profile.get.js and causes exception on user profile page when user status has been set. 40147: French: Cloud Translation update from Gloria (based on EN rev38372) 40173: Merged V4.1-BUG-FIX to CLOUD1 38178: ALF-14720: Get rid of big, unused class 40174: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: 40092: Changing the color of the "Synced Folder" link for each theme 40094: Property translation problem fixed: If there is a replacement parameter (e.g. {0}) and an apostrophe in a property the apostrophe needs to be escaped by using a double apostrophe otherwise the property cannot be translated correctly. 40172: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 40072: Error message string updates, based on feedback from Docs. 40076: Fixes encoding error introduced in r40072. 40169: CloudSync: fix ALF-15380 - source file is deleted (or unsync with delete) but an unpulled target update causes target to remain synced (and/or not deleted) 40343: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 40333: Probable fix for ALF-15430. sys:hidden aspect needs to be handled consistently by Hybrid Sync. 40363: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: 40356: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 40176: CloudSync: add simple rename test (file + folder sync) 40282: CloudSync: ALF-15468 - Can't create cloud sync connection from Enterprise 4.1 to Alfresco "in the cloud" ! 40328: CloudSync: minor - unit test updates (for simple move of directly synced nodes - file or folder) 40333: Probable fix for ALF-15430. sys:hidden aspect needs to be handled consistently by Hybrid Sync. 40334: Removing unused import - also want to trigger rebuild on this plan as I suspect it failed randomly. 40349: CloudSync: fix move out (=> target delete) - add unit tests ( ALF-14655 / ALF-15011 ) 40402: Fix to CSS error in cloudsync merge - incorrectly overridden font-size in H3 dashlet contents (fix from Mr "I read CSS in my sleep" Hatfield). 40421: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 40419: Fix for the MLText parts of ALF-15502. 44550: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 40565: CLOUD-23: core changes to allow overriding of "edit online" button's URL generation. 40566: CLOUD-23: override "edit online" button's URL generation to work with cloud format SPP URLs. 40628: Cloud SPP: stop SOAP/RPC requests being rejected when URL is outside of doclib. 40653: CLOUD-26: 4.1 All protocol features outside browsing the document library should be disabled 40667: CLOUD-26: core change to support both Thor and standard URL schemes. 40668: CLOUD-26: fix Document Management sidebar before disabling unwanted features. 40670: CLOUD-26: fixing "add new members..." to work with cloud (before disabling feature!) 40677: Cloud SPP: fix disallowing of unsafe methods outside of doclib, fixed to allow all methods to Sharpoint protocol methods and fixed tests. 40683: CLOUD-26: core changes to allow easy overriding of dwsServiceHandler bean without having to respecify configuration settings. 40686: CLOUD-26: disable Members tab in Document Management sidebar of Office 2007. 40693: CLOUD-26: disable Links tab of the Document Management sidebar in Office 2007. 40814: Cloud SPP: users were unable to list/edit files within their non-home tenant. 40846: CLOUD-28: made activity "appTool" parameter a configurable value. 44551: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 40861: Merged HEAD to BRANCHES/DEV/CLOUD1_SP: 40854: ALF-15557: Sharepoint subsystem to create activity records 40863: Cloud SPP: Removed activity posting code made redundant by commit 40861 41037: CLOUD-23: SPP can be deployed to the root context, i.e. "/" 41079: CLOUD-26: core SPP changes to allow overriding of behaviour. 41080: CLOUD-26: disable "documents" tab in document management sidebar of Office 2007. 44552: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 41102: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 40553: Remove on-premise style MT store Id extraction code from syncmode config lookup. 41103: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 40783: Merged BRANCHES/V4.1 to BRANCHES/DEV/CLOUD1: 40782: Fix ALF-15420: Move: child files/subfolders aren't synced after moving from parent folder and updating in Alfresco on-premise/Cloud 41106: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 40871: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 40342: Merged BRANCHES/DEV/THOR1_REST_API1 to BRANCHES/DEV/THOR1_REST_API2: 35747: Merged BRANCHES/DEV/THOR1_REST_API to BRANCHES/DEV/THOR1_REST_API1: 34235: Upgraded Jackson to 1.9.4 34258: Removed invalid bean definitions (classes don't exist) for abstract="true" beans. 34463: Exported=true 34464: Removed unused parent workflow 34465: Added a createComment method (Just cut and pasted Neils testcase code). 34466: Initial check-in of Rest Api code. Still quite a bit to do. 34619: Added sources 34620: Going to start another Sprint so I need to check in everything up to date. WIP 34691: Renamed Action interfaces for better consistency, Fixed up POST methods with better Metadata about resources and fixed failing tests 34727: Reworked the Resource execution, added PUT webscript, improved the tests. 34853: Implemented HTTP Methods: GET, PUT, POST, DELETE with better test cases 34856: Initial cut of rest api implementations. Work in progress. 34935: Added EmbeddedEntityResource and uniqueid annotations. Recursively calls embedded entities, improved tests. 34956: Attempted simplification by removing the CollectionResource concept -> Just an EntityResource with 5 potential methods. 34986: 2nd cut of rest api implementations: still wip. 34987: Added a readById method for Resource GET using the relationship id 35007: Rest API (THOR-1150, THOR-1151, THOR-1152, THOR-1153, THOR-1220, ): - explicitly set default timezone for date serialization/deserialization to UTC - more rest api implementation tests - rest api implementation re-factoring resulting from changes to the framework 35024: Automatically sets the id of passedIn object for a HTTP PUT for relationship resources. 35051: Further impl for public api: THOR-1150 -> THOR-1153 and THOR-1220 35059: Separated the GET executor so the embedded objects are called correctly. 35102: Added Paging, fixed up the Tests. 35117: wip, rest api implementations 35312: Made final 35313: Better exception handling with messages 35314: Improved paging/params and messages, started on embedded relations 35322: Added support for relations projections, to filter by properties use "properties" param, to add in relations use the "relations" param 35329: Handles null relationship keys 35377: Checks for simple property types which don't allow embedding 35496: WIP: rest implementations, test framework and tests 35529: Initial commit of scope and versioning support. 35539: Fixed the versioning for relationship resources 35560: Quick cleanup 35562: Embedded relationships is now working much better 35586: Safer serialization of Maps/Collections 35603: Added better version and filtering tests. 35611: Added Entity and Relationship not found exceptions 35612: Added Entity and Relationship not found exceptions 35674: Rest api implementations: - node refs are represented solely by their uuid (workspace://SpacesStore is assumed) - updates to model classes (hashcodes, equals, etc) - more paging support - tests 35858: Merged BRANCHES/DEV/THOR1_REST_API to BRANCHES/DEV/THOR1_REST_API1: 35773: Added api versioning with @WebApiDeleted annotation. Better inheritance handling and improved tests. 35774: Deleted, it wasn't supposed to be there. 35988: WIP: - sample requests - more tests - general fixes, including security fixes - tag entity 35990: Merged BRANCHES/DEV/THOR1_REST_API to BRANCHES/DEV/THOR1_REST_API1: 35938: Reworked filtering in a big way. Hopefully now it works. 35982: Clean up and bug fix the request 35983: Clean up and bug fix the request 36002: Merged BRANCHES/DEV/THOR1_REST_API to BRANCHES/DEV/THOR1_REST_API1: 35992: Clean up after discussions with David/Steve. New Parameter called "parameters"! 36056: Clean up, not used 36057: Changed the way responses are rendered, more consistent "entry" entries. 36087: Changed the json structure for tests as well 36134: Added paging parameter, cleaned up imports, more tests 36146: WIP: - fixed up create tag return type - fixed up Person model entity so that it doesn't extend Node 36151: Fix for Serialization of recursive related Collection of results 36168: Better empty collection handling, initial sorting tests. 36189: Fixes embedded resources by correct id. 36210: WIP: - updates to rest api request files - changed Comment so that it doesn't extend Node. removed title - more tests 36211: WIP: - service paging and other fixes and updates 36298: Moved jetty files to fix the build (it's odd that cloud -r works and yet continuous.xml does not) 36299: Update classpath as a result of jetty jars move 36301: Fix to stop null values in hashmaps from being returned in results. 36302: Added copyright notices 36303: WIP: - split site service into a base class (dealing with site permissions and membership) and a subclass (everything else) to aid the following - moved site membership functionality into canned queries for person sites and site members relations - changed totalItems to an Integer so that it can be null if total items is not known - added siteId filtering for activities - copyright notices - creating a comment through the comment service raises a share-like activity 36304: WIP: - changed totalItems to an Integer so that it can be null if total items is not known - copyright notices 36309: Servlet API 2.5 needs to be included at the top for the embedded jetty to work - need to revisit this. 36310: Temporary fix for dealing with nodeRefs embedded in activity summaries: regex match and convert to NodeRef. 36312: Fix tests 36325: Add jetty jars to ant test classpath 36327: Site memberships fix, override search subsystem to force lucene, re-instate full site service implementation (Spring circular dependencies are ok) 36335: Add missing file 36367: Fixes to tests 36368: CommentService fix - permissions. 36438: WIP: - Person filtering (for cloud) - Disabled some tests that are preventing the test from running (will revisit) - Person-avatar relation 36522: WIP: - fix sort ordering for site members - fix site id filter for activity feed list (add tenant id) 36523: WIP: - more tests - improved robustness of tests - added status mappings for EntityNotFound and RelationResourceEntityNotFound - for avatar, return base 64 encoded avatar data and avatar node id - use more specific EntityNotFound and RelationResourceEntityNotFound exceptions. 36646: Changes resulting from review - make sure empty strings and null values are not output - person entity tidy-up - person visibility rules applied to person retrieval - more tests - return noderef of avatar node 36668: Fix build, add missing file 36694: Fix failing test 36764: WIP: - site containers canned query - node ratings: return null average (rather than -1) if there are no ratings - site membership canned query sorting - use an enum for sort fields 36767: Add missing files 36769: WIP: - node ratings: remove user rating summary, added rating date and fixed up tests 36770: WIP: - some changes to property names for activity feed entries 36823: WIP: - re-instate -1 for average rating when there are no ratings 36824: Changes resulting from review: - merge activities into one relation - quota model object to model network quotas - Change HomeAccount -> HomeNetwork - Changes to comment property names - If average rating is -1 (from the rating service) convert to null - remove readById from node entity resource - Favourite sites url renaming 36825: Removed debug logging for rest api 36826: Commented out tag related tests until I figure out how to get the lucene search working 36833: Added discoverablity for the documentation. New @WebApiDescription, @WebApiParam annotations 36871: Added tests for @WebApiDecrition, @WebApiParam 37026: WIP: - added discoverability annotations - changes resulting from review: o node ratings output restructure o network quotas restructure (need to revisit this again) - activity summary post processing framework - added non-caching to rest api webscript (need to revisit) - don't output empty strings (need to revisit) - more tests 37058: WIP: - change tag service support 37141: WIP: - tag sorting - fix JSON issue in PreferencesService 37142: WIP: - refactoring: split functional areas into separate REST API implementation classes - added copyright notices - network quotas represented as a list - ensure empty (zero length) JSON strings coming into the REST api are treated as null 37300: WIP: - support for update comment - activity summary processing - web script set no caching and response content type 37301: WIP: - support for update comment 37354: Build fix: try rebuilding indexes 37369: Fix build: try again 37498: More logging to determine build failure using lucene for search 37608: Build fix? 37609: Fix build 37637: Fix build: try again 37672: Build fix (try again) 37860: Get networks: consolidate functionality so that it's easily callable from core Thor code and public api code 37861: Get networks: consolidate functionality so that it's easily callable from core Thor code and public api code 37862: Fix for THOR-1493: "OpenCMIS Change Object Id to be Node GUID" 37864: Fix for THOR-1494: "OpenCMIS Change Base Url to be Consistent With Public Rest Api" 37866: Unit test fix 37950: Build fix for ApiTest (another try - override repository-properties bean to force lucene) 38051: Build/test fix (another try) 38072: Removed file no longer required 38114: Better representation of the Resource metadata. Try calling: /alfresco/service/public/alfresco/versions/1 38230: Fix PublicApiTest on the build machine 38309: Public api test fixes 38319: Public api webscript transaction configuration. Make get webscript r/o transaction. 38320: Public rest api: make sure http response headers are written before the response content. 38340: More build test fixes 38349: Fix for THOR-1493: "OpenCMIS Change Object Id to be Node GUID" - fix up CMIS query fallout 38530: Fix tests 38531: Fix for THOR-1493: "OpenCMIS Change Object Id to be Node GUID" - fix up cmis queries 38552: Further test fixes 38574: Added some comments 38730: Added OPTIONS webscript to get metadata on the current url 38879: Refactored a little to make the code easier to extend / customise 38880: Fixed incorrect config, I should try not to delete things. 38986: Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/THOR1_REST_API1: 38954: Fix for ALF-14475: "CMIS : Wrong cmisra:numItems in folder sites and below with /cmisatom binding url" 39010: Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/THOR1_REST_API1: 39006: Fix for ALF-14475 part 2: "CMIS : Wrong cmisra:numItems in folder sites and below with /cmisatom binding url" 39090: Fixed broken webscript OPTIONS call 39159: Fix broken INFO url mapping 39326: Clean up, Added ResourceMetaDataWriter interface for different meta info presentation formats 39331: The tests need their own context 39609: Update to latest Chemistry libraries 39657: Fix for CLOUD-417, part 1: "Delete a Comment" 39741: Fix for CLOUD-409: "Change Endpoint for CMIS URLs in Staging" 39742: Fix for CLOUD-409: "Change Endpoint for CMIS URLs in Staging" 39750: Fix for CLOUD-417, part 2: "Delete a Comment" - added delete comment method to comment service 39754: Fix for CLOUD-417, part 3: "Delete a Comment" - fixed up activity generation for update and delete comment 39776: OpenCMIS TCK fix: getDescendants is using node service, which unlike the FileFolderService does not automatically remove hidden files. 39780: Shifted public api opencmis webscript definitions to Thor 39781: Shifted public api opencmis webscript definitions to Thor 39782: Fix for CLOUD-409: "Change Endpoint for CMIS URLs in Staging" 39783: Minor fixes, comments 39785: Public api tests - move jetty libraries to 3rd party - remove JettyComponent as a Spring bean and change how it's constructed 39786: Fix for THOR-1493: "OpenCMIS Change Object Id to be Node GUID" - for unversioned documents, the revision number should not be appended 39794: Added missing files for public api tests 39795: Update to latest Chemistry libraries 39834: Fix for THOR-1493: "OpenCMIS Change Object Id to be Node GUID" - for unversioned documents (or documents with version "1.0"), the revision number should not be appended 39835: Fix for CLOUD-362: "Discover Networks" API should be at "/tenants" 39836: Fix for THOR-1493: "OpenCMIS Change Object Id to be Node GUID" 39844: Public api tests - move jetty libraries to 3rd party - remove JettyComponent as a Spring bean and change how it's constructed 39854: Fix for CLOUD-362: "Discover Networks" API should be at "/tenants" 39856: Re-instate tests. 39857: Fix for THOR-1494: "OpenCMIS Change Base Url to be Consistent With Public Rest Api" - removed cmisatom url binding (opencmis is now at .../public/cmis/versions/1/atom) 39926: Fix for CLOUD-442: "Public API : Tagging + Paging" 39927: Fix for CLOUD-442: "Public API : Tagging + Paging" - updates to public api tests 40185: Make sure LockService is injected into CommentService 40186: Make sure LockService is injected into CommentService 40187: Changes resulting from sprint demo: - changes to network and network membership representation - embed modifiedBy person object in comment 40211: Changes resulting from sprint demo: - changes to network and network membership representation 40212: Fix intermittently failing test 40223: Fix for THOR-1494: "OpenCMIS Change Base Url to be Consistent With Public Rest Api" - changes resulting from end of spring demo (get repositories located at .../cmis/versions/1.0/atom) - removed browser binding for now 40224: Fix for THOR-1494: "OpenCMIS Change Base Url to be Consistent With Public Rest Api" (Thor-specific changes) - changes resulting from end of spring demo (get repositories located at .../cmis/versions/1.0/atom) - removed browser binding for now 40225: Fix for THOR-1494: "OpenCMIS Change Base Url to be Consistent With Public Rest Api" - changes resulting from end of spring demo (get repositories located at .../cmis/versions/1.0/atom) - removed browser binding for now 40254: Fix for THOR-1494: "OpenCMIS Change Base Url to be Consistent With Public Rest Api" - changes resulting from end of spring demo (get repositories located at .../cmis/versions/1.0/atom) 40255: Fix for THOR-1494: "OpenCMIS Change Base Url to be Consistent With Public Rest Api" - changes resulting from end of spring demo (get repositories located at .../cmis/versions/1.0/atom) 40256: Fix for THOR-1494: "OpenCMIS Change Base Url to be Consistent With Public Rest Api" - changes resulting from end of spring demo (get repositories located at .../cmis/versions/1.0/atom) 40270: Turned stacktrace into a String 40339: CLOUD-488: "The activities feed in the public API is not returning all required summary data for comments" 40548: Merged PATCHES/V4.0.2 to BRANCHES/DEV/THOR1_REST_API2: 40526: Fixed ALF-15540: CMIS: Synchronized block in service interceptor 40644: Public api: 40645: Public Api: 40646: Public Api: 40649: Public api: 40650: Public api: 40657: Merged BRANCHES/DEV/THOR1_REST_API1 to BRANCHES/DEV/THOR1_REST_API2: 40515: CLOUD-390: Content models to be filtered out of Cloud CMIS implementation 40550: Public api: - updates to datetime property names 40552: Public api: - sort sites by site title (by default) 40596: Removed "system" from exclusions for CLOUD-495 40610: CLOUD-495: F414: Add multiple tags to a folder or document 40674: Public api: 40675: THOR-1324: "F414: Add multiple tags to a folder or document" 40676: Public api: 40678: Public api: 40715: Public api: 40730: Merged /alfresco/BRANCHES/DEV/THOR1_REST_API1:r40724 Better meta-data handling for PUT requests 40731: Merged /alfresco/BRANCHES/DEV/AMILLER/CLOUD-496:r40659: CLOUD-500: Add trusted auth with key validation. 40732: Fixed failing test 40739: Merged BRANCHES/DEV/V3.4-BUG-FIX:r40193: ALF-15307 / ALF-15190: FTS indexing can stick forever on purged nodes - May cut 50 minutes off the build time when merged 40742: Public api 40801: Public api: 40802: Public api 40803: Public api 40804: Public api 40807: Public api 40811: Public api 40815: Public api: 40826: Public api 41108: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 40898: Fix build 41110: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 40903: Fixed solrcore properties (merge error) 41111: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41007: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 40998: CLOUD-563: "View external member of the site if internal user is not a member of a site." 41112: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41048: Merged DEV/V4.1-BUG-FIX to DEV/CLOUD1 40382: Fix for ALF-15491 SOLR is generating queries for lucene style cross-language support 40632: Fix for ALF-15487 Search not working for queries containing 3-digit versions Fix for ALF-15356 SOLR doesn't support searching by cm:name of file with underscore and dots 40662: Eclipse classpath fixes 41032: Fix for ALF-15753 Infinite loop during Solr ACL indexing when ACL Changeset batch is empty 41245: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41128: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: 41003: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs 41026: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs 41039: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs 41086: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs 41115: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs 41126: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 40364: Testcase for ALF-15178, which is not reproduced. 40419: Fix for the MLText parts of ALF-15502. 40782: Fix ALF-15420: Move: child files/subfolders aren't synced after moving from parent folder and updating in Alfresco on-premise/Cloud 41127: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 41121: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/V4.1: 41003: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs 41026: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs 41039: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs 41086: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs 41123: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/V4.1: 41115: CloudSync: ALF-15734 - force unsync (of last SSMN) on target causes repeating pull errors to appear in both logs 41247: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41149: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 41146: Fix trusted authentication against OpenCMIS 41248: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41157: BDE-73: finish adding all the code into Sonar report. Tests are not there yet. 41249: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41180: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: 40482: ALF-13998: 'No items' error is highlighted in red, even that is not sever error. - ALF-15453: Incorrect manage permissions working for a file/folder Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/CLOUD1: 40486: ALF-15453: Incorrect manage permissions working for a file/folder 41250: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41185: BDE-73: Add back web-framework-commons to fix reporting 41251: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41192: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 41191: Fix for OpenCMIS trusted authentication to allow local opencmis access as before 41252: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41209: BDE-73: workaround MDEP-98 encountered with Sonar 41253: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41212: CLOUD-610: "Public APIs causing problem with mobile app" 44553: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 41293: Cloud SPP only: fix IDE project dependency and classpath for PublicApiTest (and others no doubt) to run. 41294: Cloud SPP: ensure vti and thor module dependency is handled correctly. 41312: Fix build problem: some unit tests had been split out and needed the sharepoint module on the classpath. 41379: Cloud SPP: remove remote-api dependency from repository. 44554: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 41695: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41114: Merged DEV\V4.1-BUG-FIX to DEV\CLOUD1 40977: (Record only) Sync parser generation 41696: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41302: CLOUD-622: "Mobile App connection to testmy build 227 issue" 41698: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41375: BDE-73: remove never-ending test for reporting 41699: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41377: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 40859: public api: 40862: CLOUD-555: "Updating comment fails because node not found" 40961: CLOUD-566: "Site object shouldn't contain the "sitePreset" property" 40979: Added tests for non-numeric params and non-european characters 41030: Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/THOR1_REST_API2: 41027: ALF-15193: "Folder Is Not a Folder CmisInvalidArgumentException thrown when using cmis:objectId" 41097: Added quickshare and cloudsync to cmis filter list 41098: Renamed serializer/deserializer lists for RestJsonModule 41099: Cleaned up consistent Rest responses - CLOUD-574 41100: Meta info webscript is disabled because its not been reviewed / in the correct format 41104: Meta info webscript is really disabled because its not been reviewed / in the correct format 41105: Added key for trusted layer7 relationship 41137: Public api: 41168: Public apI; 41169: CLOUD-565: "API / URL returns a "tenant required" error" 41170: public api: 41171: public api: 41172: public api: 41183: CLOUD-598: "API: "GET network" response has unnecessary JSON nesting" 41186: Fix solrcore properties 41282: CLOUD-616: "CMIS API: Repository names should be more descriptive" 41283: CLOUD-601: "API /networkId/public/cmis/versions/ returns a 400" 41325: Public api: 41367: CLOUD-636: "REST API: Nodes: Tags: Not allowed DELETE method proceeds with status 204" 41369: Public api: 41700: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41474: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 41422: Sample OAuth call for the Resource Owner Password flow 41427: Sample OAuth call for the Client Credentials flow 41433: Public api: (1) parameterize site members/people sorting in service api 41434: Public api: minor comment service fix 41438: CLOUD-629: "REST API: It is possible to add a comment to comment" 41439: Public api: fix for default tenant id in CMIS requests 41449: CLOUD-629: "REST API: It is possible to add a comment to comment" 41456: CLOUD-644: "Performing a Checkout in OpenCMIS Results in an Exception in the Share Doc Library" 41457: Fix build 41461: Public api: more tests 41701: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41560: CLOUD-657: "cmis root service document returning incorrect urls" 41702: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41627: CLOUD-676: "OpenCMIS Returns Error 500 on GET: https://testp.alfresco.me/alfresco/a/cmis/versions/1.0/atom" 41703: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41670: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 41521: CLOUD-660: "GET favourite sites results in exception when skipCount is greater than number of items" 41606: Fix for CLOUD-668 - POSTing to a relationship by id now returns an InvalidArgumentException 41607: CLOUD-668 : changed ordering so errors fire in correct order. 41628: Public api: more tests 44555: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 41709: CLOUD-640: Notification is not displayed for consumer for updating/editing document added by other user 42331: Cloud SPP: really fix import order. 42364: Cloud SPP: fix Spring bean filename typo. 43078: CLOUD-868: incorrect URL for Sharepoint protocol 43153: CLOUD-747: SPP: Checking document out doesn't work 43221: CLOUD-896: Unable to open specific version of document from MS Word 43222: CLOUD-896: Unable to open specific version of document from MS Word 43247: CLOUD-896: missing class CloudVersionsServiceHandler from previous commit. 43256: CLOUD-896: fix broken test 44556: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 43472: (RECORD ONLY) Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/CLOUD1_SP: 41710: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 41672: CLOUD-672: "404 Error when browsing child files/folder " 41673: Public api: more tests 41675: CLOUD-674: "Root network calls need to return application/json content type" 41754: Merged BRANCHES/DEV/FEATURES/CLOUD1_LAYER7 to BRANCHES/DEV/CLOUD1: 41677: No-op skeletons of Public API gateway filter and URL rewrite filter 41683: Url rewrite rules for new and old-style public api 41705: First working version of gateway filter 41732: Trim authenticator keys from config 41734: Productising code - tests and bug fixing with layer7, code tidyup, configuration, error handling 41753: SSL support for accepting self-signed certificates 41794: Merged BRANCHES/DEV/FEATURES/CLOUD1_LAYER7 to BRANCHES/DEV/CLOUD1: 41792: Fixes for Gateway authentication filter after end to end testing on staging. 41798: Merged BRANCHES/DEV/FEATURES/CLOUD1_LAYER7 to BRANCHES/DEV/CLOUD1: 41797: Ensure authentication filter provides enough info to gateway for full analytics 41815: Merged BRANCHES/DEV/FEATURES/CLOUD1_LAYER7 to BRANCHES/DEV/CLOUD1: 41814: Fix regression where old style apigee route failed 41842: CLOUD-619: "CMIS query for objectId throws exception" 41843: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 41790: CLOUD-629: "REST API: It is possible to add a comment to comment" 41791: CLOUD-629: "REST API: It is possible to add a comment to comment" 41793: CLOUD-629: "REST API: It is possible to add a comment to comment" 41823: Public api: more tests 41825: Fix for CLOUD-684 - wraps the list, not sure if I like the paging 41855: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 41853: CLOUD-700: "The mobile SDK needs a way to map an accessToken to a user identifier" 42005: Merged BRANCHES/DEV/FEATURES/CLOUD1_DAM to BRANCHES/DEV/CLOUD1: 37644: Resolve CLOUD-34: Incorporate Gallery View 39105: Moved to AMP produced from modules/dam/BRANCHES/V1.0 with overrideSource=cloud build property 39268: ALF-13984: High Resolution Mime-Aware Place Holder Icons - Implemented in DAM 1.0.2.0 39651: Deployed DAM module version 1.0.3 to CLOUD1_DAM which fixes: - CLOUD-380: Document library displayed incorrectly in Gallery view in IE7 - DAM-36: CLONE - Document library displayed incorrectly in Gallery view in IE7 - DAM-37: Gallery View: No Border Around Selected Items in IE7 - DAM-38: Gallery View: Two Items Must be Selected in IE7 for Multi-select Actions - DAM-39: Gallery View: Slider Behavior is Erratic in IE7 - DAM-40: Gallery View: Upload Instructions Throws Error in IE7 40083: CLOUD-378: Drag and drop within document library doesn't work in Gallery view - Deployed DAM 1.0.4.0 40507: Changed name of DAM module to Media Management 40534: Version 1.0.5.1 of MM module which includes: - DAM-61: Multi-Select Checkboxes Are No Longer Present in 1.0.5 40723: Deployed version 1.0.5.3 of the Media Management module which includes fixes for: - CLOUD-544 / DAM-66: 'Share' Link Displayed Incorrectly on Info Panel - CLOUD-543 / DAM-65: Gallery View Content Display Should Support Dynamic Changing of Browser Width 41174: Deployed Media Management module 1.0.5.4 which includes: - DAM-67: Gallery View Changes the Size of Dragging Thumbnails in Other Views - DAM-68: Gallery VIew Should Hide Its Container on destroyView - DAM-69: Gallery View Resizing Should Fire Only When Resizing has Stopped - DAM-71: Gallery View Thumbnail Generation Should Accept Thumbnail Definition Parameter 41536: Merged HEAD to BRANCHES/DEV/FEATURES/CLOUD1_DAM: 40870: m.share fixes 41537: Merged HEAD to BRANCHES/DEV/FEATURES/CLOUD1_DAM: 40919: m.share fixes 41538: Merged HEAD to BRANCHES/DEV/FEATURES/CLOUD1_DAM: 41432: Fixed CLOUD-587 "Drag and Drop Changes Conflict with Hover Events on Touch Devices" 41614: Changed version of gallery view only MM module to 0.1 42004: Upgraded to MM 0.1.5.5 which contains fix for DAM-74 42047: Merged DEV/CLOUD_SIGN_UP into DEV/CLOUD1 (cloud priority 42049: Merged DEV/CLOUD1-BUG-FIX into DEV/CLOUD1: 41674: ALF-15967: Using START_USER_ID_ instead of "initiator" property to query process instances started by user X to prevent extra joins + removed unused constants 41650: Fixed CLOUD-667: Merged fix for ALF-14438 into CLOUD1-BUG-FIX + using START_USER_ID_ instead of custom "initiator" property to query initiator to boost performance even more 42050: Merged DEV/CLOUD1-BUG-FIX into DEV/CLOUD1: 41674: ALF-15967: Using START_USER_ID_ instead of "initiator" property to query process instances started by user X to prevent extra joins + removed unused constants 41650: Fixed CLOUD-667: Merged fix for ALF-14438 into CLOUD1-BUG-FIX + using START_USER_ID_ instead of custom "initiator" property to query initiator to boost performance even more 42121: Merged BRANCHES/DEV/AMILLER/CLOUD1 to BRANCHES/DEV/CLOUD1: 39663: CLOUD-153 - Invite box doesn't parse email addresses properly 42160: Merged BRANCHES/DEV/AMILLER/CLOUD1 to BRANCHES/DEV/CLOUD1: 38864: CLOUD-41 - Resize logo banner on upload 38878: CLLOUD-42 - Image previews should be larger 42199: Merged BRANCHES/DEV/CLOUD1-BUG-FIX to BRANCHES/DEV/CLOUD1: 41113: Creating new [bug fix] branch from BRANCHES/DEV/CLOUD1 41211: QuickShare: CLOUD-593: prevent copying of QuickShare props on node copy [+ minor refactor] 41612: QuickShare: Merged form THOR1_QUICK_SHARE to CLOUD1-BUG-FIX 36363: QuickShare: Improvment for THOR-1268 "F378: As a user I can choose to share a document" 41639: QuickShare: Fixed bug where the display of quickshare was based on the showComments property 42200: Merged DEV/CLOUD1-BUG-FIX into DEV/CLOUD1: Record-only (r41650 and r41674) 42201: Merged DEV/CLOUD1-BUG-FIX into DEV/CLOUD1: Record-only (r41650 and r41674) 42203: CLOUD-71: Patch was there, but reference to it from "postUpgrade..." was lost in merge, preventing it from being executed 42204: Merged BRANCHES/DEV/HEAD_QUICK_SHARE_TMP to BRANCHES/DEV/CLOUD1: 41733: QuickShare: fix ShareContentGet NPE (fix for Document Details redirect if logged in) 42206: Merged BRANCHES/DEV/CLOUD1-BUG-FIX to BRANCHES/DEV/CLOUD1: 41213: CLOUD-452: reconfigure Activities DAOs to use common Alfresco datasource 41214: CLOUD-452: reconfigure Activities DAOs to use common Alfresco datasource (DO NOT MERGE to Enterprise/HEAD) 41270: CLOUD-452 / ALF-15823: reconfigure Activities DAOs to use common Alfresco datasource 42209: Merged BRANCHES/DEV/CLOUD1-BUG-FIX to BRANCHES/DEV/CLOUD1: 41292: CLOUD-520: Convert TicketCleanupJob to a StatfulJob 41410: CLOUD-119: Change status code to 409 (Conflict), from 400 (Bad Request), when parent folder does not exist 42211: Fix pesky solrcore.properties ... remove hardcoded amiller ;-) 42231: CLOID-71: TEMP - comment-out previous r42203 (breaks build/tests) 42241: Updated Media Management module to 0.1.5.6 which contains fixes for: - CLOUD-740/MM-76: Empty space in the page footer when switching to Gallery View - CLOUD-741/MM-75: IE7: Opacity of Folder Icon not Rendered Correctly in Gallery View - CLOUD-741/MM-77: Gallery View Continually Recalculates Height in IE - CLOUD-753/MM-78: Links for 'Create a folder' and 'Upload files' in the Empty Folder Instructions Don't Work in Gallery View - MM-79: Disable Fade Animation on Resize of Gallery View 42252: Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/CLOUD1 42233: Fix for ALF-16164 Cloud monitoring of SOLR is CPU intensive due to its repeated use of the SOLR stats page and related CLOUD-760 Cloud monitoring of SOLR is CPU intensive due to its repeated use of the SOLR stats page 42267: CLOUD-153: Update CloudInvitationServiceImpl to only use the address part of an email string and ignore the personal name. 42285: Merged BRANCHES/DEV/THOR1_REST_API2 to BRANCHES/DEV/CLOUD1: 41867: CLOUD-711: "It should be possible to remove a favourite site" 41957: Public api: - error handling improvements - more tests 41970: Public api: - fix build (add missing files) 41972: Public api: - more tests (including OpenCMIS TCK) 42046: Added some debug timings. Enabled using log4j.logger.org.alfresco.rest.framework.webscripts=debug 42058: Public api: changed logging package name 42124: Public api: - more tests - minor fixes 42125: CLOUD-734: "CMIS: Query Issues Discovered During Testing" 42126: Public api: fix up OpenCMIS tests 42171: Consistency fix for CLOUD-695. 42180: Fix build 42181: CLOUD-695 "Requests to un-implemented urls do not consistently return http status 405 (Method not allowed)": fix up public api tests 42214: Public api tests (cmis) 42251: Changed the serializer for embedded entities, (related to CLOUD-746) 42253: Fix build 42288: Fix build - fix merge problem 42302: Updated Media Management module to 0.1.5.7 which contains fixes for: - CLOUD-788/MM-89: "Rename" Icon is not Displayed Correctly in Gallery View 42320: Fix build (fix dodgy merge) 42334: Fixed build of Media Management module for: - CLOUD-789/MM-91: MM AMPs Produced No Longer Contain module.properties 42341: Fixed CLOUD-755 "Label needs to be renamed and icon should be present" 42344: Fixed CLOUD-755 "Label needs to be renamed and icon should be present" part 2 42365: CLOUD-153: Fix additional invite paths. 42373: Updated Media Management module to 0.1.5.8 which contains: - MM-92: Change Default View for Cloud to Detailed Rather than Gallery 42513: CLOUD-808: fixed issue with timers due when alfresco boots 42577: CLOUD-808: Fix for timer deploying MT-process when shared is required caused test to fail 42588: BDE-73: fix compilation + switch to MySQL to make cloud tests pass 42595: Add salesforce modules to privatemodules. Contains only models. 42596: Add salesforce module build properites and ant build tasks. 42601: Updated salesforce amp with correct version number and updated module-context.xml 42668: BDE-73 Fix Sonar reporting 42709: FRENCH: Translation updates based on EN r42416 42867: CLOUD-71: add explicit schema number for workflow timers redeploy patch 42871: GERMAN: Cloud Translation, based on r 42416 42879: SPANISH: Cloud Translation, based on r 42416 42890: ITALIAN: Cloud Translation, based on r 42416 43120: Merged BRANCHES/DEV/CLOUD1_CORS to BRANCHES/DEV/CLOUD1: 43100: Update the salesforce amp to include the CORS Filter 43101: Update web.xml to enable to the CORS Filter with filter-mapping 43117: Add updated amp with removed CORS Filter. CORS Filter is now available in 3rd-party libs 43118: [CLOUD-724] Add CORS Filter jar to 3rd-party libs 43119: [CLOUD-724] Add missing jar java-property-utils-1.6.jar to 3rd-party libs 43243: BDE-73: Fork for each test, otherwise some fail because of previous test leftovers 43323: Merged BRANCHES/DEV/FEATURES/CLOUD1_GRANULARPERMISSIONS1 to BRANCHES/DEV/CLOUD1: 42257: CLOUD-649: Updates UI action for Granular Permissions 42295: CLOUD-33: Updates labels 42303: CLOUD-651: Limit user search to site members only. 42352: CLOUD-780: Adds a rolesWhitelist to only display those roles that are allowed. Currently set to: ["Consumer", "Contributor", "Collaborator"]. This is only used in the site view. The Repository view behaviour has not changed. 42427: Removes multi-select manage-permissions dialogue. 42520: CLOUD-802: Change the way the user is returned to the previous page to work around suspected Chrome bug. 42604: CLOUD-840: Automatically add SiteManger permisions when inheritAll is switched off. Pervent remaoval of siteManager authority. 42606: Fixed CLOUD-840 "Granular Permissions: Site Manager should retain access when inherit permissions is turned off" 42807: CLOUD-863: Reordered code to avoid AccessDeniedException 43259: CLOUD-901: Revert showComments check so it handles undefined permission 43266: CLOUD-900: Old Manage permissions form is opened via Permissions panel 43307: CLOUD-900: Follow-up fix 43353: Fix for remote transform server with new cloud environment 43457: Merged BRANCHES/DEV/FEATURES/CLOUD1_GOOGLEDOCS to BRANCHES/DEV/CLOUD1: 39869: Merged BRANCHES/V4.1 to BRANCHES/DEV/FEATURES/CLOUD1_GOOGLEDOCS: Merged /integrations/GoogleDocs/BRANCHES/THOR1_SPRINTS_GOOGLEDOCS to BRANCHES/V4.1 36307 Fixed THOR-1402 "Need a callback option for Create Content menu" - Will be revisited to add better comments 36350 Fixed THOR-1402 "Need a callback option for Create Content menu" part 2 - Changed callback obj to match action object better: { nodeRef, node, jsNode } - Better code documentation - Removed dev/test code - Improved error config handling 36358 THOR-1402 "Need a callback option for Create Content menu" part 3 - Changed "link" parameter to "href" (instead of "page") to match actions. 39256 Consistency fix of doclib & repo toolbar templates after work on THOR-1402 39260 Bugfix for "destination" parameter not being resolved correctly THOR-1402 39875: Configurtion changes to enable Google Docs V2 within the web tier. 39881: It's extension ... not extention 39938: Update CLOUD1_GOOGLEDOCS ant build to include installing googledocs amp files 39944: CLOUD-437: Ensure that custom doclib action resources are requested after documentlist.js 40468: Alfresco GoogleDocs AMPS. Fixes for [GOOGLEDOCS-73], [GOOGLEDOCS-70], [GOOGLEDOCS-75], [GOOGLEDOCS-76] 40624: Update Google Docs AMPs 40635: Merge Google Docs Cloud AMPS to Feature Branchs 40824: Update Google Docs AMPS, fixing [GOOGLEDOCS-79] 40897: Google Docs AMP update 41165: Merge Updated AMPs for [GOOGLEDOCS-83] 41179: Update AMPs for [GOOGLEDOCS-87] 41262: Update AMPs for GOOGLEDOCS-89 41493: Update AMPs for [GOOGLEDOCS-93] 41635: AMP update for [GOOLGEDOCS-98] [GOOGLEDOCS-99] [GOOGLEDOCS-100] 41805: Update AMPs for [GOOGLEDOCS-101] 41840: Add AMPs with i18n strings 43316: (RECORD ONLY) Rebase CLOUD1_GOOGLEDOCS with CLOUD1 43317: (RECORD ONLY) Re-merge build.properties and projects.xml to include googledocs amps in build 43320: Add googledocs repo declaration 44557: Merged BRANCHES/DEV/CLOUD1_SP to BRANCHES/DEV/FEATURES/CLOUD1_SP_CLEAN: 43751: CLOUD-747: SPP: Checking document out doesn't work 43765: CLOUD-747: fixes ${localname} substitution in vti.server.external.host value 43780: CLOUD-747: Sharepoint version of PROPFIND handler must return 'href' containing the externally visible base URL 43785: CLOUD-747: fix for proxied repository - more robust parsing of URLs. 44001: Fix pesky solrcore properties 44036: Resolve CLOUD-877: UI button for SharePoint Protocol needs to be updated 44211: CLOUD-747: checkout/checkin works for spaces in document 44564: Fix construction of vti activity processor after merge 44565: Remove unnecessary build of war and amps for exploded target (NOTE: may fail in continuous build) 47521: Fix merge - WebDAVHelper - fix NPE on login (missing hiddenAspect injection) 47645: Fix merge - build/test - RenditionServiceIntegrationTest.testBuiltinRenditionDefinitions - junit.framework.AssertionFailedError: expected: but was: - manual/partial re-merge of CLOUD-42 - note: thumbnail code merge/converge will required additional review and testing 47971: Fix merge issue for SPP WebDAV 48059: Redo the WebDAVHelper merge the way it was done on HEAD to enable dependency injection again (aka 'un-butcher') - This will temporarily break CLOUD2 compilation. I will fix that next. 48061: Enable easier overide of webDavHelper by defining an abstract bean definition 48084: CLOUD-1448: Make it possible to override beans defined in remote-api-context.xml within modules by adjusting the loading order 48087: CLOUD-1356: Fixed failing Activiti MT-test 48128: Add small script to install build result to local Maven repo 48427: CLOUD-1356: Stabilized MT workflow-functionality after cloud-convergence 48496: CONV: fix CLOUD-1483 - rework exclusion of admin@tenant - to fix failed CLOUD2 test (NetworkAdminTest testPaging) 48499: CONV: fix CLOUD-1483 - rework exclusion of admin@tenant - to fix failed CLOUD2 test (NetworkAdminTest testPaging) - follow-on to fix boundary condition 48501: CONV: fix CLOUD-1479 "Invite People" - new user in network (that does not exist yet) - initial fix (pending regress fix from CLOUD-1458) 48508: CONV: fix CLOUD-1483 / CLOUD-1407 - rework exclusion of admin@tenant - to fix failed CLOUD2 test (NetworkAdminTest testPaging) - test and fix expected total count (if requested) 48511: CONV: fix CLOUD-1479 "Invite People" - new user in network (that does not exist yet) - follow-on fix (pending regress fix from CLOUD-1458) 48533: Merged CONV_V413 to CONV_HEAD 48524: CONV: CONV_V413 - comment test back in ... ... RenditionServiceIntegrationTest.testRenderFreeMarkerTemplate" 48534: Remove duplicate old joda time version 48535: Fixed merge issue 48539: Fix MailActionExecuter handling of non-tenant users 48544: Hopeful build fix - restore Jan's domain logic git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@48546 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .externalToolBuilders/JibX.launch | 68 ++-- config/alfresco/activiti-context.xml | 18 +- config/alfresco/application-context.xml | 3 + config/alfresco/bootstrap-context.xml | 6 + config/alfresco/content-services-context.xml | 2 +- config/alfresco/core-services-context.xml | 7 + config/alfresco/hazelcast/hazelcast-ec2.xml | 183 +++++++++++ config/alfresco/hazelcast/hazelcast-tcp.xml | 180 ++++++++++ config/alfresco/hazelcast/hazelcast-udp.xml | 179 ++++++++++ config/alfresco/mt/mt-base-context.xml | 3 - .../alfresco/preference-service-context.xml | 2 +- config/alfresco/repository.properties | 50 ++- config/alfresco/script-services-context.xml | 2 + .../default/openoffice-transform-context.xml | 2 +- config/alfresco/thumbnail-service-context.xml | 283 ++++++++-------- config/alfresco/tx-cache-context.xml | 2 +- config/alfresco/webdav-context.xml | 33 +- config/alfresco/workflow-context.xml | 3 + .../opencmis/AlfrescoCmisServiceImpl.java | 311 +++++++++++------- .../org/alfresco/opencmis/CMISConnector.java | 15 +- .../alfresco/opencmis/CMISNodeInfoImpl.java | 14 +- .../alfresco/repo/action/ActionTestSuite.java | 75 ++--- .../action/executer/MailActionExecuter.java | 166 ++++++---- .../executer/MailActionExecuterTest.java | 14 +- .../repo/activities/ActivityServiceImpl.java | 17 +- .../activities/feed/FeedNotifierImpl.java | 21 +- .../activities/post/lookup/PostLookup.java | 1 - .../cluster/HazelcastConfigFactoryBean.java | 172 ++++++++++ .../HazelcastConfigFactoryBeanTest.java | 69 ++++ .../cluster/HazelcastInstanceFactory.java | 139 ++++++++ .../repo/importer/ImporterComponent.java | 31 +- .../repo/mail/AlfrescoJavaMailSender.java | 1 - .../org/alfresco/repo/model/Repository.java | 3 +- .../RenditionServiceIntegrationTest.java | 5 +- .../security/person/GetPeopleCannedQuery.java | 54 ++- .../security/person/PersonServiceImpl.java | 22 +- .../repo/security/person/PersonTest.java | 76 ++++- .../security/person/TestPersonManager.java | 14 +- .../alfresco/repo/site/SiteServiceImpl.java | 23 +- .../repo/tenant/MultiTAdminServiceImpl.java | 28 +- .../alfresco/repo/tenant/MultiTDemoTest.java | 127 +++---- .../repo/tenant/MultiTServiceImpl.java | 4 +- .../repo/tenant/TenantRoutingDataSource.java | 4 +- .../repo/thumbnail/ThumbnailRegistry.java | 80 +++++ .../repo/webdav/LockStoreFactoryImpl.java | 79 +++++ .../repo/webdav/WebDavServiceImpl.java | 5 +- .../AbstractMultitenantWorkflowTest.java | 9 +- .../repo/workflow/WorkflowObjectFactory.java | 16 +- .../repo/workflow/WorkflowServiceImpl.java | 11 + .../repo/workflow/WorkflowTestSuite.java | 1 + .../AbstractActivitiComponentTest.java | 34 +- .../workflow/activiti/ActivitiConstants.java | 4 +- .../activiti/ActivitiEngineInitializer.java | 39 ++- .../ActivitiMultitenantWorkflowTest.java | 1 + .../activiti/ActivitiTypeConverter.java | 79 ++++- .../repo/workflow/activiti/ActivitiUtil.java | 43 ++- .../activiti/ActivitiWorkflowEngine.java | 260 +++++++-------- .../ActivitiWorkflowManagerFactory.java | 47 +-- ...ctivitiWorkflowServiceIntegrationTest.java | 1 - .../activiti/AlfrescoBpmnParseListener.java | 22 +- .../AlfrescoProcessEngineConfiguration.java | 9 +- .../AuthenticatedTimerJobHandler.java | 123 +++++-- .../properties/ActivitiPropertyConverter.java | 2 + .../repo/workflow/jbpm/JBPMEngine.java | 2 +- .../service/cmr/workflow/WorkflowService.java | 7 + .../util/ApplicationContextHelper.java | 1 - .../test-activiti-component-context.xml | 17 +- .../cluster-test/placeholder-test.xml | 166 ++++++++++ 68 files changed, 2575 insertions(+), 915 deletions(-) create mode 100644 config/alfresco/hazelcast/hazelcast-ec2.xml create mode 100644 config/alfresco/hazelcast/hazelcast-tcp.xml create mode 100644 config/alfresco/hazelcast/hazelcast-udp.xml create mode 100644 source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBean.java create mode 100644 source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBeanTest.java create mode 100644 source/java/org/alfresco/repo/cluster/HazelcastInstanceFactory.java create mode 100644 source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java create mode 100644 source/test-resources/cluster-test/placeholder-test.xml diff --git a/.externalToolBuilders/JibX.launch b/.externalToolBuilders/JibX.launch index 186d90d901..209a3cdd97 100644 --- a/.externalToolBuilders/JibX.launch +++ b/.externalToolBuilders/JibX.launch @@ -1,34 +1,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/activiti-context.xml b/config/alfresco/activiti-context.xml index c91dc97b24..691440e49a 100644 --- a/config/alfresco/activiti-context.xml +++ b/config/alfresco/activiti-context.xml @@ -60,6 +60,9 @@ + + ${system.workflow.deployWorkflowsInTenant} + @@ -90,7 +93,7 @@ - + @@ -123,13 +126,12 @@ - + - - + @@ -228,12 +230,6 @@ factory-method="getManagementService" /> - - - - - - + diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index 4506f69de6..3c046a0300 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -16,6 +16,9 @@ --> + + + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index a58806787c..43008c4aef 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -835,4 +835,10 @@ + + + + + diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 155df3f97a..92ea231226 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -367,7 +367,7 @@ - + cm:expiryDate sys:clientVisibilityMask + cm:lastThumbnailModification + cm:likesRatingSchemeTotal + cm:likesRatingSchemeCount + cm:fiveStarRatingSchemeCount + cm:fiveStarRatingSchemeTotal + + fm:commentCount diff --git a/config/alfresco/hazelcast/hazelcast-ec2.xml b/config/alfresco/hazelcast/hazelcast-ec2.xml new file mode 100644 index 0000000000..b4a3f8b7bd --- /dev/null +++ b/config/alfresco/hazelcast/hazelcast-ec2.xml @@ -0,0 +1,183 @@ + + + + ${alfresco.cluster.name} + ${alfresco.hazelcast.password} + + + 5701 + + + 224.2.2.3 + 54327 + + + 127.0.0.1 + + + ${alfresco.hazelcast.ec2.accesskey} + ${alfresco.hazelcast.ec2.secretkey} + + ${alfresco.hazelcast.ec2.region} + + ${alfresco.hazelcast.ec2.securitygroup} + ${alfresco.hazelcast.ec2.tagkey} + ${alfresco.hazelcast.ec2.tagvalue} + + + + 10.10.1.* + + + + PBEWithMD5AndDES + + thesalt + + thepass + + 19 + + + + RSA/NONE/PKCS1PADDING + + thekeypass + + local + + JKS + + thestorepass + + keystore + + + + 16 + 64 + 60 + + + + 0 + + default + + + + 1 + + 0 + + 0 + + NONE + + 0 + + 25 + + hz.ADD_NEW_ENTRY + + + + + 1 + ${alfresco.hazelcast.conccurrentuser.timeToLive} + 0 + LRU + 0 + 25 + hz.ADD_NEW_ENTRY + 15 + + + + + + + + log4j + false + false + false + + + \ No newline at end of file diff --git a/config/alfresco/hazelcast/hazelcast-tcp.xml b/config/alfresco/hazelcast/hazelcast-tcp.xml new file mode 100644 index 0000000000..8fa5a13238 --- /dev/null +++ b/config/alfresco/hazelcast/hazelcast-tcp.xml @@ -0,0 +1,180 @@ + + + + ${alfresco.cluster.name} + ${alfresco.hazelcast.password} + + + 5701 + + + 224.2.2.3 + 54327 + + + ${alfresco.hazelcast.tcp.config} + 127.0.0.1 + + + my-access-key + my-secret-key + + us-west-1 + + hazelcast-sg + type + hz-nodes + + + + 10.10.1.* + + + + PBEWithMD5AndDES + + thesalt + + thepass + + 19 + + + + RSA/NONE/PKCS1PADDING + + thekeypass + + local + + JKS + + thestorepass + + keystore + + + + 16 + 64 + 60 + + + + 0 + + default + + + + 1 + + 0 + + 0 + + NONE + + 0 + + 25 + + hz.ADD_NEW_ENTRY + + + + + 1 + ${alfresco.hazelcast.conccurrentuser.timeToLive} + 0 + LRU + 0 + 25 + hz.ADD_NEW_ENTRY + 15 + + + + + + + + log4j + + \ No newline at end of file diff --git a/config/alfresco/hazelcast/hazelcast-udp.xml b/config/alfresco/hazelcast/hazelcast-udp.xml new file mode 100644 index 0000000000..4b6765eda9 --- /dev/null +++ b/config/alfresco/hazelcast/hazelcast-udp.xml @@ -0,0 +1,179 @@ + + + + ${alfresco.cluster.name} + ${alfresco.hazelcast.password} + + + 5701 + + + 224.2.2.3 + 54327 + + + 127.0.0.1 + + + my-access-key + my-secret-key + + us-west-1 + + hazelcast-sg + type + hz-nodes + + + + 10.10.1.* + + + + PBEWithMD5AndDES + + thesalt + + thepass + + 19 + + + + RSA/NONE/PKCS1PADDING + + thekeypass + + local + + JKS + + thestorepass + + keystore + + + + 16 + 64 + 60 + + + + 0 + + default + + + + 1 + + 0 + + 0 + + NONE + + 0 + + 25 + + hz.ADD_NEW_ENTRY + + + + + 1 + ${alfresco.hazelcast.conccurrentuser.timeToLive} + 0 + LRU + 0 + 25 + hz.ADD_NEW_ENTRY + 15 + + + + + + + + log4j + + \ No newline at end of file diff --git a/config/alfresco/mt/mt-base-context.xml b/config/alfresco/mt/mt-base-context.xml index 562a9fb310..2983ce4ccc 100644 --- a/config/alfresco/mt/mt-base-context.xml +++ b/config/alfresco/mt/mt-base-context.xml @@ -33,10 +33,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -213,12 +66,136 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/tx-cache-context.xml b/config/alfresco/tx-cache-context.xml index 7c0699742b..fc9fc48918 100644 --- a/config/alfresco/tx-cache-context.xml +++ b/config/alfresco/tx-cache-context.xml @@ -575,7 +575,7 @@ - org.alfresco.tenantsTransactionalCache + org.alfresco.cache.tenantEntityTransactionalCache diff --git a/config/alfresco/webdav-context.xml b/config/alfresco/webdav-context.xml index 5d938f577e..a45954e5ae 100644 --- a/config/alfresco/webdav-context.xml +++ b/config/alfresco/webdav-context.xml @@ -25,7 +25,38 @@ - + + + + + + + + + + + ${alfresco.cluster.name} + ${alfresco.hazelcast.password} + ${alfresco.hazelcast.specify.interface} + ${alfresco.hazelcast.bind.interface} + + ${alfresco.hazelcast.tcp.config} + + ${alfresco.hazelcast.ec2.accesskey} + ${alfresco.hazelcast.ec2.secretkey} + ${alfresco.hazelcast.ec2.region} + ${alfresco.hazelcast.ec2.securitygroup} + ${alfresco.hazelcast.ec2.tagkey} + ${alfresco.hazelcast.ec2.tagvalue} + + ${alfresco.conccurrentusers.timeToLive} + + + + + + + \ No newline at end of file diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index ea9d1f7018..a1f9c6068b 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -37,6 +37,9 @@ ${system.workflow.maxPooledTasks} + + ${system.workflow.deployWorkflowsInTenant} + diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java index f7728011f7..3a32bfb062 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java @@ -19,6 +19,8 @@ package org.alfresco.opencmis; import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.InputStream; import java.io.Serializable; import java.math.BigInteger; @@ -69,6 +71,7 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.Pair; +import org.alfresco.util.TempFileProvider; import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.data.Acl; import org.apache.chemistry.opencmis.commons.data.AllowableActions; @@ -156,78 +159,6 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr objectInfoMap = new HashMap(); } - @Override - public void beforeCall() - { - AuthenticationUtil.pushAuthentication(); - if (authentication != null) - { - // Use the previously-obtained authentication - AuthenticationUtil.setFullAuthentication(authentication); - } - else - { - if (context == null) - { - // Service not opened, yet - return; - } - // Sticky sessions? - if (connector.openHttpSession()) - { - // create a session -> set a cookie - // if the CMIS client supports cookies that might help in clustered environments - ((HttpServletRequest) getContext().get(CallContext.HTTP_SERVLET_REQUEST)).getSession(); - } - - // Authenticate - if (authentication != null) - { - // We have already authenticated; just reuse the authentication - AuthenticationUtil.setFullAuthentication(authentication); - } - else - { - // First check if we already are authenticated - if (AuthenticationUtil.getFullyAuthenticatedUser() == null) - { - // We have to go to the repo and authenticate - String user = getContext().getUsername(); - String password = getContext().getPassword(); - Authorization auth = new Authorization(user, password); - if (auth.isTicket()) - { - connector.getAuthenticationService().validate(auth.getTicket()); - } - else - { - connector.getAuthenticationService().authenticate(auth.getUserName(), auth.getPasswordCharArray()); - } - } - this.authentication = AuthenticationUtil.getFullAuthentication(); - } - -// // TODO: How is the proxy user working. -// // Until we know what it is meant to do, it's not available -// String currentUser = connector.getAuthenticationService().getCurrentUserName(); -// String user = getContext().getUsername(); -// String password = getContext().getPassword(); -// if (currentUser != null && currentUser.equals(connector.getProxyUser())) -// { -// if (user != null && user.length() > 0) -// { -// AuthenticationUtil.setFullyAuthenticatedUser(user); -// } -// } - } - } - - @Override - public void afterCall() - { - AuthenticationUtil.popAuthentication(); - } - @Override public void open(CallContext context) { @@ -571,10 +502,10 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { try { - if(connector.filter(child.getNodeRef())) - { - continue; - } + if(connector.filter(child.getNodeRef())) + { + continue; + } // create a child CMIS object CMISNodeInfo ni = createNodeInfo(child.getNodeRef()); @@ -711,19 +642,19 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr if(isFolder && type.getAlfrescoClass().equals(ContentModel.TYPE_SYSTEM_FOLDER)) { - continue; + continue; } - if(connector.isHidden(child.getChildRef())) - { - continue; - } + if(connector.isHidden(child.getChildRef())) + { + continue; + } - if(connector.filter(child.getChildRef())) - { - continue; - } - + if(connector.filter(child.getChildRef())) + { + continue; + } + // create a child CMIS object ObjectInFolderDataImpl object = new ObjectInFolderDataImpl(); CMISNodeInfo ni = createNodeInfo(child.getChildRef()); @@ -1000,11 +931,11 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { for (NodeRef nodeRef : nodeRefs) { - // TODO - perhaps filter by path in the query instead? - if(connector.filter(nodeRef)) - { - continue; - } + // TODO - perhaps filter by path in the query instead? + if(connector.filter(nodeRef)) + { + continue; + } if (skipCounter > 0) { @@ -1197,7 +1128,10 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr throw new CmisConstraintException("This document type is not versionable!"); } - final Charset encoding = getEncoding(contentStream); + // copy stream to temp file + // OpenCMIS does this for us .... + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); FileInfo fileInfo = connector.getFileFolderService().create( parentInfo.getNodeRef(), name, type.getAlfrescoClass()); @@ -1214,7 +1148,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr String mimeType = stripEncoding(contentStream.getMimeType()); writer.setMimetype(mimeType); writer.setEncoding(encoding.name()); - writer.putContent(contentStream.getStream()); + writer.putContent(tempFile); } connector.extractMetadata(nodeRef); @@ -1224,6 +1158,8 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr connector.applyVersioningState(nodeRef, versioningState); + removeTempFile(tempFile); + String objectId = connector.createObjectId(nodeRef); connector.getActivityPoster().postFileFolderAdded(fileInfo); @@ -1406,12 +1342,21 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr throw new CmisInvalidArgumentException("No content!"); } - final Charset encoding = getEncoding(contentStream); + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = getEncoding(tempFile, contentStream.getMimeType()); - ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); - writer.setMimetype(contentStream.getMimeType()); - writer.setEncoding(encoding.name()); - writer.putContent(contentStream.getStream()); + try + { + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + } + finally + { + removeTempFile(tempFile); + } objectId.setValue(connector.createObjectId(nodeRef)); @@ -1583,9 +1528,9 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr if(info.hasPWC()) { - // is a checked out document. If a delete, don't allow unless checkout is canceled. If a cancel - // checkout, not allowed. - throw new CmisConstraintException( + // is a checked out document. If a delete, don't allow unless checkout is canceled. If a cancel + // checkout, not allowed. + throw new CmisConstraintException( "Could not delete/cancel checkout on the original checked out document"); } @@ -1936,7 +1881,9 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr final NodeRef nodeRef = info.getNodeRef(); final TypeDefinitionWrapper type = info.getType(); - final Charset encoding = getEncoding(contentStream); + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); // check in // update PWC @@ -1952,7 +1899,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); writer.setMimetype(contentStream.getMimeType()); writer.setEncoding(encoding.name()); - writer.putContent(contentStream.getStream()); + writer.putContent(tempFile); } // check aspect @@ -1979,6 +1926,8 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), newNodeRef); objectId.setValue(connector.createObjectId(newNodeRef)); + + removeTempFile(tempFile); } @Override @@ -2454,15 +2403,15 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr protected String getGuid(String nodeId) { - int idx = nodeId.lastIndexOf("/"); - if(idx != -1) - { - return nodeId.substring(idx+1); - } - else - { - return nodeId; - } + int idx = nodeId.lastIndexOf("/"); + if(idx != -1) + { + return nodeId.substring(idx+1); + } + else + { + return nodeId; + } } /** @@ -2693,25 +2642,137 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr } } - private Charset getEncoding(ContentStream stream) + private Charset getEncoding(File tempFile, String mimeType) { Charset encoding = null; - if(stream != null) + try { - try - { - String mimeType = stream.getMimeType(); - InputStream tfis = new BufferedInputStream(stream.getStream()); - ContentCharsetFinder charsetFinder = connector.getMimetypeService().getContentCharsetFinder(); - encoding = charsetFinder.getCharset(tfis, mimeType); - tfis.close(); - } catch (Exception e) - { - throw new CmisStorageException("Unable to read content: " + e.getMessage(), e); - } + InputStream tfis = new BufferedInputStream(new FileInputStream(tempFile)); + ContentCharsetFinder charsetFinder = connector.getMimetypeService().getContentCharsetFinder(); + encoding = charsetFinder.getCharset(tfis, mimeType); + tfis.close(); + } catch (Exception e) + { + throw new CmisStorageException("Unable to read content: " + e.getMessage(), e); } return encoding; } + + private File copyToTempFile(ContentStream contentStream) + { + if (contentStream == null) + { + return null; + } + + File result = null; + try + { + result = TempFileProvider.createTempFile(contentStream.getStream(), "cmis", "content"); + } + catch (Exception e) + { + throw new CmisStorageException("Unable to store content: " + e.getMessage(), e); + } + + if ((contentStream.getLength() > -1) && (result == null || contentStream.getLength() != result.length())) + { + removeTempFile(result); + throw new CmisStorageException("Expected " + contentStream.getLength() + " bytes but retrieved " + + (result == null ? -1 :result.length()) + " bytes!"); + } + + return result; + } + + private void removeTempFile(File tempFile) + { + if (tempFile == null) + { + return; + } + + try + { + tempFile.delete(); + } + catch (Exception e) + { + // ignore - file will be removed by TempFileProvider + } + } + + @Override + public void beforeCall() + { + AuthenticationUtil.pushAuthentication(); + if (authentication != null) + { + // Use the previously-obtained authentication + AuthenticationUtil.setFullAuthentication(authentication); + } + else + { + if (context == null) + { + // Service not opened, yet + return; + } + // Sticky sessions? + if (connector.openHttpSession()) + { + // create a session -> set a cookie + // if the CMIS client supports cookies that might help in clustered environments + ((HttpServletRequest) getContext().get(CallContext.HTTP_SERVLET_REQUEST)).getSession(); + } + + // Authenticate + if (authentication != null) + { + // We have already authenticated; just reuse the authentication + AuthenticationUtil.setFullAuthentication(authentication); + } + else + { + // First check if we already are authenticated + if (AuthenticationUtil.getFullyAuthenticatedUser() == null) + { + // We have to go to the repo and authenticate + String user = context.getUsername(); + String password = context.getPassword(); + Authorization auth = new Authorization(user, password); + if (auth.isTicket()) + { + connector.getAuthenticationService().validate(auth.getTicket()); + } + else + { + connector.getAuthenticationService().authenticate(auth.getUserName(), auth.getPasswordCharArray()); + } + } + this.authentication = AuthenticationUtil.getFullAuthentication(); + } + +// // TODO: How is the proxy user working. +// // Until we know what it is meant to do, it's not available +// String currentUser = connector.getAuthenticationService().getCurrentUserName(); +// String user = getContext().getUsername(); +// String password = getContext().getPassword(); +// if (currentUser != null && currentUser.equals(connector.getProxyUser())) +// { +// if (user != null && user.length() > 0) +// { +// AuthenticationUtil.setFullyAuthenticatedUser(user); +// } +// } + } + } + + @Override + public void afterCall() + { + AuthenticationUtil.popAuthentication(); + } } diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java index 1ac4dd6604..57636c0234 100644 --- a/source/java/org/alfresco/opencmis/CMISConnector.java +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -71,7 +71,6 @@ import org.alfresco.repo.security.permissions.impl.AccessPermissionImpl; import org.alfresco.repo.security.permissions.impl.ModelDAO; import org.alfresco.repo.tenant.TenantAdminService; import org.alfresco.repo.tenant.TenantDeployer; -import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.repo.thumbnail.ThumbnailHelper; import org.alfresco.repo.thumbnail.ThumbnailRegistry; @@ -260,7 +259,6 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen private RenditionService renditionService; private FileFolderService fileFolderService; private TenantAdminService tenantAdminService; - private TenantService tenantService; private TransactionService transactionService; private AuthenticationService authenticationService; private PermissionService permissionService; @@ -307,15 +305,15 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen private ObjectFilter objectFilter; + // -------------------------------------------------------------- + // Configuration + // -------------------------------------------------------------- + public void setObjectFilter(ObjectFilter objectFilter) { this.objectFilter = objectFilter; } - // -------------------------------------------------------------- - // Configuration - // -------------------------------------------------------------- - /** * Sets the root store. * @@ -342,11 +340,6 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen return activityPoster; } - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - public void setHiddenAspect(HiddenAspect hiddenAspect) { this.hiddenAspect = hiddenAspect; diff --git a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java index ee609d3328..38d690a50b 100644 --- a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java +++ b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java @@ -303,8 +303,20 @@ public class CMISNodeInfoImpl implements CMISNodeInfo { objecVariant = CMISObjectVariant.CURRENT_VERSION; } + + // Is it un-versioned, or currently versioned? + Version currentVersion = connector.getVersionService().getCurrentVersion(nodeRef); + if (currentVersion != null) + { + versionLabel = currentVersion.getVersionLabel(); + versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + } + else + { + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + } + objectId = getGuid(currentNodeId) + CMISConnector.ID_SEPERATOR + CMISConnector.UNVERSIONED_VERSION_LABEL; - versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; currentObjectId = objectId; hasPWC = (connector.getLockService().getLockType(nodeRef) == LockType.READ_ONLY_LOCK); } else diff --git a/source/java/org/alfresco/repo/action/ActionTestSuite.java b/source/java/org/alfresco/repo/action/ActionTestSuite.java index e67e08da96..f2fdc2984f 100644 --- a/source/java/org/alfresco/repo/action/ActionTestSuite.java +++ b/source/java/org/alfresco/repo/action/ActionTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -18,9 +18,6 @@ */ package org.alfresco.repo.action; -import junit.framework.Test; -import junit.framework.TestSuite; - import org.alfresco.repo.action.evaluator.CompareMimeTypeEvaluatorTest; import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluatorTest; import org.alfresco.repo.action.evaluator.HasAspectEvaluatorTest; @@ -28,50 +25,48 @@ import org.alfresco.repo.action.evaluator.IsSubTypeEvaluatorTest; import org.alfresco.repo.action.executer.AddFeaturesActionExecuterTest; import org.alfresco.repo.action.executer.ContentMetadataEmbedderTest; import org.alfresco.repo.action.executer.ContentMetadataExtracterTest; +import org.alfresco.repo.action.executer.MailActionExecuterTest; import org.alfresco.repo.action.executer.RemoveFeaturesActionExecuterTest; import org.alfresco.repo.action.executer.SetPropertyValueActionExecuterTest; import org.alfresco.repo.action.executer.SpecialiseTypeActionExecuterTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; /** - * Version test suite + * Action test suite * * @author Roy Wetherall + * @author Alex Miller */ -public class ActionTestSuite extends TestSuite +@RunWith(Suite.class) +@SuiteClasses({ + ParameterDefinitionImplTest.class, + ActionDefinitionImplTest.class, + ActionConditionDefinitionImplTest.class, + ActionImplTest.class, + ActionConditionImplTest.class, + CompositeActionImplTest.class, + ActionServiceImplTest.class, + CompositeActionConditionImplTest.class, + + // Test evaluators + IsSubTypeEvaluatorTest.class, + ComparePropertyValueEvaluatorTest.class, + CompareMimeTypeEvaluatorTest.class, + HasAspectEvaluatorTest.class, + + // Test executors + SetPropertyValueActionExecuterTest.class, + AddFeaturesActionExecuterTest.class, + ContentMetadataExtracterTest.class, + ContentMetadataEmbedderTest.class, + SpecialiseTypeActionExecuterTest.class, + RemoveFeaturesActionExecuterTest.class, + ActionTrackingServiceImplTest.class, // intermittent - pending ALF-9773 & ALF-9774 + MailActionExecuterTest.class +}) +public class ActionTestSuite { - /** - * Creates the test suite - * - * @return the test suite - */ - public static Test suite() - { - TestSuite suite = new TestSuite(); - suite.addTestSuite(ParameterDefinitionImplTest.class); - suite.addTestSuite(ActionDefinitionImplTest.class); - suite.addTestSuite(ActionConditionDefinitionImplTest.class); - suite.addTestSuite(ActionImplTest.class); - suite.addTestSuite(ActionConditionImplTest.class); - suite.addTestSuite(CompositeActionImplTest.class); - suite.addTestSuite(ActionServiceImplTest.class); - suite.addTestSuite(CompositeActionConditionImplTest.class); - - // Test evaluators - suite.addTestSuite(IsSubTypeEvaluatorTest.class); - suite.addTestSuite(ComparePropertyValueEvaluatorTest.class); - suite.addTestSuite(CompareMimeTypeEvaluatorTest.class); - suite.addTestSuite(HasAspectEvaluatorTest.class); - - // Test executors - suite.addTestSuite(SetPropertyValueActionExecuterTest.class); - suite.addTestSuite(AddFeaturesActionExecuterTest.class); - suite.addTestSuite(ContentMetadataExtracterTest.class); - suite.addTestSuite(ContentMetadataEmbedderTest.class); - suite.addTestSuite(SpecialiseTypeActionExecuterTest.class); - suite.addTestSuite(RemoveFeaturesActionExecuterTest.class); - suite.addTestSuite(ActionTrackingServiceImplTest.class); - - return suite; - } } diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index 76fc9d5f84..202eea48da 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java @@ -111,7 +111,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase /** * The java mail sender */ - private JavaMailSender javaMailSender; + private JavaMailSender mailService; /** * The Template service @@ -196,7 +196,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase */ public void setMailService(JavaMailSender javaMailSender) { - this.javaMailSender = javaMailSender; + this.mailService = javaMailSender; } /** @@ -243,7 +243,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { this.authorityService = authorityService; } - + /** * @param nodeService the NodeService to set. */ @@ -251,7 +251,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { this.nodeService = nodeService; } - + /** * @param tenantService the TenantService to set. */ @@ -259,7 +259,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { this.tenantService = tenantService; } - + /** * @param headerEncoding The mail header encoding to set. */ @@ -267,7 +267,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { this.headerEncoding = headerEncoding; } - + /** * @param fromAddress The default mail address. */ @@ -307,6 +307,12 @@ public class MailActionExecuter extends ActionExecuterAbstractBase this.sendTestMessage = sendTestMessage; } + /** + * This stores an email address which, if it is set, overrides ALL email recipients sent from + * this class. It is intended for dev/test usage only !! + */ + private String testModeRecipient; + /** * Send a test message * @@ -334,11 +340,8 @@ public class MailActionExecuter extends ActionExecuterAbstractBase Action ruleAction = serviceRegistry.getActionService().createAction(NAME, params); - // TODO review & test converged code (and remove comment below) !! - prepareAndSendEmail(ruleAction, null); - - /* - MimeMessageHelper message = prepareEmail(ruleAction, null); + MimeMessageHelper message = prepareEmail(ruleAction, null, + new Pair(testMessageTo, getLocaleForUser(testMessageTo)), getFrom(ruleAction)); try { mailService.send(message.getMimeMessage()); @@ -362,17 +365,10 @@ public class MailActionExecuter extends ActionExecuterAbstractBase Object[] args = {testMessageTo, txt.toString()}; throw new AlfrescoRuntimeException("email.outbound.err.send.failed", args, me); } - */ return true; } - /** - * This stores an email address which, if it is set, overrides ALL email recipients sent from - * this class. It is intended for dev/test usage only !! - */ - private String testModeRecipient; - public void setTestModeRecipient(String testModeRecipient) { this.testModeRecipient = testModeRecipient; @@ -503,8 +499,13 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } } - private void prepareAndSendEmail(final Action ruleAction, final NodeRef actionedUponNodeRef, final Pair recipient, final Pair sender) + public MimeMessageHelper prepareEmail(final Action ruleAction , final NodeRef actionedUponNodeRef, final Pair recipient, final Pair sender) { + // Create the mime mail message. + // Hack: using an array here to get around the fact that inner classes aren't closures. + // The MimeMessagePreparator.prepare() signature does not allow us to return a value and yet + // we can't set a result on a bare, non-final object reference due to Java language restrictions. + final MimeMessageHelper[] messageRef = new MimeMessageHelper[1]; MimeMessagePreparator mailPreparer = new MimeMessagePreparator() { @SuppressWarnings("unchecked") @@ -515,7 +516,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase logger.debug(ruleAction.getParameterValues()); } - MimeMessageHelper message = new MimeMessageHelper(mimeMessage); + messageRef[0] = new MimeMessageHelper(mimeMessage); // set header encoding if one has been supplied if (headerEncoding != null && headerEncoding.length() != 0) @@ -527,7 +528,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase String to = (String)ruleAction.getParameterValue(PARAM_TO); if (to != null && to.length() != 0) { - message.setTo(to); + messageRef[0].setTo(to); } else { @@ -595,7 +596,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase if(recipients.size() > 0) { - message.setTo(recipients.toArray(new String[recipients.size()])); + messageRef[0].setTo(recipients.toArray(new String[recipients.size()])); } else { @@ -620,8 +621,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase // from is enabled if (! authService.isCurrentUserTheSystemUser()) { - String currentUserName = authService.getCurrentUserName(); - fromPerson = getPerson(currentUserName); + fromPerson = personService.getPerson(authService.getCurrentUserName()); } if(isFromEnabled()) @@ -641,17 +641,17 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { try { - message.setFrom(from, fromPersonalName); + messageRef[0].setFrom(from, fromPersonalName); } catch (UnsupportedEncodingException error) { // Uses the JVM's default encoding, can never be unsupported. Just in case, revert to simple email - message.setFrom(from); + messageRef[0].setFrom(from); } } else { - message.setFrom(from); + messageRef[0].setFrom(from); } } else @@ -669,14 +669,15 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { logger.debug("looked up email address for :" + fromPerson + " email from " + fromActualUser); } - message.setFrom(fromActualUser); + messageRef[0].setFrom(fromActualUser); } else { // from system or user does not have email address - message.setFrom(fromDefaultAddress); + messageRef[0].setFrom(fromDefaultAddress); } } + } else { @@ -685,17 +686,20 @@ public class MailActionExecuter extends ActionExecuterAbstractBase logger.debug("from not enabled - sending from default address:" + fromDefaultAddress); } // from is not enabled. - message.setFrom(fromDefaultAddress); + messageRef[0].setFrom(fromDefaultAddress); } + + + // set subject line - message.setSubject((String)ruleAction.getParameterValue(PARAM_SUBJECT)); + messageRef[0].setSubject((String)ruleAction.getParameterValue(PARAM_SUBJECT)); if ((testModeRecipient != null) && (testModeRecipient.length() > 0) && (! testModeRecipient.equals("${dev.email.recipient.address}"))) { // If we have an override for the email recipient, we'll send the email to that address instead. // We'll prefix the subject with the original recipient, but leave the email message unchanged in every other way. - message.setTo(testModeRecipient); + messageRef[0].setTo(testModeRecipient); String emailRecipient = (String)ruleAction.getParameterValue(PARAM_TO); if (emailRecipient == null) @@ -709,9 +713,10 @@ public class MailActionExecuter extends ActionExecuterAbstractBase String recipientPrefixedSubject = "(" + emailRecipient + ") " + (String)ruleAction.getParameterValue(PARAM_SUBJECT); - message.setSubject(recipientPrefixedSubject); + messageRef[0].setSubject(recipientPrefixedSubject); } + // See if an email template has been specified String text = null; @@ -767,18 +772,18 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { // If we have an override for the email recipient, we'll send the email to that address instead. // We'll prefix the subject with the original recipient, but leave the email message unchanged in every other way. - message.setTo(testModeRecipient); + messageRef[0].setTo(testModeRecipient); String emailRecipient = recipient.getFirst(); String recipientPrefixedSubject = "(" + emailRecipient + ") " + localizedSubject; - message.setSubject(recipientPrefixedSubject); + messageRef[0].setSubject(recipientPrefixedSubject); } else { - message.setTo(recipient.getFirst()); - message.setSubject(localizedSubject); + messageRef[0].setTo(recipient.getFirst()); + messageRef[0].setSubject(localizedSubject); } } @@ -810,39 +815,50 @@ public class MailActionExecuter extends ActionExecuterAbstractBase isHTML = true; } } - + if (text != null) { - message.setText(text, isHTML); + messageRef[0].setText(text, isHTML); } } }; + MimeMessage mimeMessage = mailService.createMimeMessage(); + try + { + mailPreparer.prepare(mimeMessage); + } catch (Exception e) + { + // We're forced to catch java.lang.Exception here. Urgh. + if (logger.isInfoEnabled()) + { + logger.warn("Unable to prepare mail message. Skipping.", e); + } + } + + return messageRef[0]; + } + + private void prepareAndSendEmail(final Action ruleAction, final NodeRef actionedUponNodeRef, final Pair recipient, final Pair sender) + { + MimeMessageHelper preparedMessage = prepareEmail(ruleAction, actionedUponNodeRef, recipient, sender); try { // Send the message unless we are in "testMode" - if(!testMode) + if (!testMode) { - javaMailSender.send(mailPreparer); + mailService.send(preparedMessage.getMimeMessage()); + onSend(); } else { - try { - MimeMessage mimeMessage = javaMailSender.createMimeMessage(); - mailPreparer.prepare(mimeMessage); - lastTestMessage = mimeMessage; - } catch(Exception e) { - // We're forced to catch java.lang.Exception here. Urgh. - if (logger.isInfoEnabled()) - { - logger.warn("Unable to prepare mail message. Skipping.", e); - } - } + lastTestMessage = preparedMessage.getMimeMessage(); } } catch (MailException e) { + onFail(); String to = (String)ruleAction.getParameterValue(PARAM_TO); if (to == null) { @@ -860,7 +876,8 @@ public class MailActionExecuter extends ActionExecuterAbstractBase Boolean ignoreError = (Boolean)ruleAction.getParameterValue(PARAM_IGNORE_SEND_FAILURE); if (ignoreError == null || ignoreError.booleanValue() == false) { - throw new AlfrescoRuntimeException("Failed to send email to:" + to, e); + Object[] args = {to, e.toString()}; + throw new AlfrescoRuntimeException("email.outbound.err.send.failed", args, e); } } } @@ -1063,9 +1080,9 @@ public class MailActionExecuter extends ActionExecuterAbstractBase ); } } - return recipients; - } - + return recipients; + } + @SuppressWarnings("deprecation") public boolean personExists(final String user) { @@ -1081,6 +1098,10 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } }, domain); } + else + { + exists = personService.personExists(user); + } return exists; } @@ -1099,6 +1120,10 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } }, domain); } + else + { + person = personService.getPerson(user); + } return person; } @@ -1113,26 +1138,35 @@ public class MailActionExecuter extends ActionExecuterAbstractBase { public Locale doWork() throws Exception { - Locale locale = null; - String localeString = (String)preferenceService.getPreference(user, "locale"); - if (localeString != null) - { - locale = StringUtils.parseLocaleString(localeString); - } - return locale; + return getLocaleForUserImpl(user); } }, domain); } + else + { + return getLocaleForUserImpl(user); + } + return locale; + } + + private Locale getLocaleForUserImpl(String user) + { + Locale locale = null; + String localeString = (String)preferenceService.getPreference(user, "locale"); + if (localeString != null) + { + locale = StringUtils.parseLocaleString(localeString); + } return locale; } private String getDomain(String user) { String[] parts = user.split("@"); - return parts.length == 1 ? "" : parts[1]; + return parts.length == 1 ? "" : parts[1].toLowerCase(I18NUtil.getLocale()); } - - /** + + /** * Return true if address has valid format * @param address * @return diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java index 49ac060822..0c02183e5a 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java @@ -50,11 +50,11 @@ public class MailActionExecuterTest { public static ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit(); // Rules to create 2 test users. - public static AlfrescoPerson AUSTRALIAN_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "EnglishUser"); - public static AlfrescoPerson BRITISH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "EnglishUser"); - public static AlfrescoPerson FRENCH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "FrenchUser"); - public static AlfrescoPerson UNKNOWN_USER1 = new AlfrescoPerson(APP_CONTEXT_INIT, "UnknownUser1"); - public static AlfrescoPerson UNKNOWN_USER2 = new AlfrescoPerson(APP_CONTEXT_INIT, "UnknowUser2"); + public static AlfrescoPerson AUSTRALIAN_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "AustralianUser@test.com"); + public static AlfrescoPerson BRITISH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "EnglishUser@test.com"); + public static AlfrescoPerson FRENCH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "FrenchUser@test.com"); + public static AlfrescoPerson UNKNOWN_USER1 = new AlfrescoPerson(APP_CONTEXT_INIT, "UnknownUser1@test.com"); + public static AlfrescoPerson UNKNOWN_USER2 = new AlfrescoPerson(APP_CONTEXT_INIT, "UnknowUser2@test.com"); // Tie them together in a static Rule Chain @ClassRule public static RuleChain ruleChain = RuleChain.outerRule(APP_CONTEXT_INIT) @@ -94,7 +94,7 @@ public class MailActionExecuterTest { preferences.clear(); preferences.put("locale", "en_AU"); - PREFERENCE_SERVICE.setPreferences(BRITISH_USER.getUsername(), preferences); + PREFERENCE_SERVICE.setPreferences(AUSTRALIAN_USER.getUsername(), preferences); } @@ -161,7 +161,7 @@ public class MailActionExecuterTest { MimeMessage message = ACTION_EXECUTER.retrieveLastTestMessage(); Assert.assertNotNull(message); - Assert.assertEquals("G'Day Jan 1, 1970", (String)message.getContent()); + Assert.assertEquals("G'Day 01/01/1970", (String)message.getContent()); } } diff --git a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java index 9dd0bd77f5..519cfb1076 100644 --- a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java +++ b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java @@ -320,6 +320,9 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean // is local to the method because we only want to cache per request - there is not point in keeping // an instance cache because the data will become stale if a user changes their avatar. Map userIdToAvatarNodeRefCache = new HashMap(); + + String currentTenantDomain = tenantService.getCurrentUserDomain(); + if (logger.isDebugEnabled()) @@ -329,12 +332,9 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean + maxFeedItems); } - String currentTenantDomain = tenantService.getCurrentUserDomain(); - for (ActivityFeedEntity activityFeed : activityFeeds) { - if (actvityFilter != null && !actvityFilter.contains(activityFeed.getActivityType())) - { + if (actvityFilter != null && !actvityFilter.contains(activityFeed.getActivityType())) { if (logger.isTraceEnabled()) { logger.trace("Filtering " + activityFeed.toString() + " \n by the activity filter."); @@ -342,8 +342,7 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean continue; } - if (userFilter != null && !userFilter.contains(activityFeed.getPostUserId())) - { + if (userFilter != null && !userFilter.contains(activityFeed.getPostUserId())) { if (logger.isTraceEnabled()) { logger.trace("Filtering " + activityFeed.toString() + " \n by the user filter."); @@ -356,10 +355,6 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean // note: pending requirements for THOR-224, for now assume all activities are within context of site and filter by current tenant if (! currentTenantDomain.equals(tenantService.getDomain(activityFeed.getSiteNetwork()))) { - if (logger.isTraceEnabled()) - { - logger.trace("Filtering " + activityFeed.toString() + " \n by the site/tenant filter."); - } continue; } } @@ -392,7 +387,7 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean { if (logger.isDebugEnabled()) { - logger.debug("getUserFeedEntries: person no longer exists: "+postUserId); + logger.warn("getUserFeedEntries: person no longer exists: "+postUserId); } } diff --git a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java index fe6a0a497c..e2d40f2705 100644 --- a/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java +++ b/source/java/org/alfresco/repo/activities/feed/FeedNotifierImpl.java @@ -38,6 +38,8 @@ import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback; import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.repo.search.SearcherException; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.admin.RepoAdminService; @@ -330,6 +332,7 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware try { final String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + final String tenantDomain = TenantUtil.getCurrentDomain(); // process the feeds using the batch processor {@link BatchProcessor} BatchProcessor.BatchProcessWorker worker = new BatchProcessor.BatchProcessWorker() @@ -355,14 +358,22 @@ public class FeedNotifierImpl implements FeedNotifier, ApplicationContextAware final RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper(); txHelper.setMaxRetries(0); - txHelper.doInTransaction(new RetryingTransactionCallback() + TenantUtil.runAsTenant(new TenantRunAsWork() { - public Void execute() throws Throwable + @Override + public Void doWork() throws Exception { - processInternal(person); - return null; + txHelper.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + processInternal(person); + return null; + } + }, false, true); + return null; } - }, false, true); + }, tenantDomain); } private void processInternal(final PersonInfo person) throws Exception 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 2ed215ad0d..2013e940ed 100644 --- a/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java +++ b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java @@ -40,7 +40,6 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.security.PermissionService; 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.NamespaceService; import org.alfresco.service.namespace.QName; diff --git a/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBean.java b/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBean.java new file mode 100644 index 0000000000..405535583c --- /dev/null +++ b/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBean.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.cluster; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.util.Properties; +import java.util.regex.Pattern; + +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; + +import com.hazelcast.config.Config; +import com.hazelcast.config.InMemoryXmlConfig; + +/** + * FactoryBean used to create Hazelcast {@link Config} objects. A configuration file is supplied + * in the form of a Spring {@link Resource} and a set of {@link Properties} can also be provided. The + * XML file is processed so that property placeholders of the form ${property.name} are substitued for + * the corresponding property value before the XML is parsed into the Hazelcast configuration object. + * + * @author Matt Ward + */ +public class HazelcastConfigFactoryBean implements InitializingBean, FactoryBean +{ + private static final String PLACEHOLDER_END = "}"; + private static final String PLACEHOLDER_START = "${"; + private Resource configFile; + private Config config; + private Properties properties; + + + /** + * Set the Hazelcast XML configuration file to use. This will be merged with the supplied + * Properties and parsed to produce a final {@link Config} object. + * @param configFile the configFile to set + */ + public void setConfigFile(Resource configFile) + { + this.configFile = configFile; + } + + /** + * Used to supply the set of Properties that the configuration file can reference. + * + * @param properties the properties to set + */ + public void setProperties(Properties properties) + { + this.properties = properties; + } + + /** + * Spring {@link InitializingBean} lifecycle method. Substitutes property placeholders for their + * corresponding values and creates a {@link Config Hazelcast configuration} with the post-processed + * XML file - ready for the {@link #getObject()} factory method to be used to retrieve it. + */ + @Override + public void afterPropertiesSet() throws Exception + { + if (configFile == null) + { + throw new IllegalArgumentException("No configuration file specified."); + } + if (properties == null) + { + properties = new Properties(); + } + + // These configXML strings will be large and are therefore intended + // to be thrown away. We only want to keep the final Config object. + String rawConfigXML = getConfigFileContents(); + String configXML = substituteProperties(rawConfigXML); + config = new InMemoryXmlConfig(configXML); + } + + /** + * For the method parameter text, replaces all occurrences of placeholders having + * the form ${property.name} with the value of the property having the key "property.name". The + * properties are supplied using {@link #setProperties(Properties)}. + * + * @param text The String to apply property substitutions to. + * @return String after substitutions have been applied. + */ + private String substituteProperties(String text) + { + for (String propName : properties.stringPropertyNames()) + { + String propValue = properties.getProperty(propName); + String quotedPropName = Pattern.quote(PLACEHOLDER_START + propName + PLACEHOLDER_END); + text = text.replaceAll(quotedPropName, propValue); + } + + return text; + } + + /** + * Opens the configFile {@link Resource} and reads the contents into a String. + * + * @return the contents of the configFile resource. + */ + private String getConfigFileContents() + { + StringWriter writer = new StringWriter(); + InputStream inputStream = null; + try + { + inputStream = configFile.getInputStream(); + IOUtils.copy(inputStream, writer, "UTF-8"); + return writer.toString(); + } + catch (IOException e) + { + throw new RuntimeException("Couldn't read configuration: " + configFile, e); + } + finally + { + try + { + if (inputStream != null) + { + inputStream.close(); + } + } + catch (IOException e) + { + throw new RuntimeException("Couldn't close stream", e); + } + } + } + + /** + * FactoryBean's factory method. Returns the config with the property key/value + * substitutions in place. + */ + @Override + public Config getObject() throws Exception + { + return config; + } + + @Override + public Class getObjectType() + { + return Config.class; + } + + @Override + public boolean isSingleton() + { + return true; + } +} diff --git a/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBeanTest.java b/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBeanTest.java new file mode 100644 index 0000000000..5a897852cf --- /dev/null +++ b/source/java/org/alfresco/repo/cluster/HazelcastConfigFactoryBeanTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005-2012 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.cluster; + +import static org.junit.Assert.assertEquals; + +import java.util.Properties; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import com.hazelcast.config.Config; + +/** + * Tests for the HazelcastConfigFactoryBean class. + * + * @author Matt Ward + */ +public class HazelcastConfigFactoryBeanTest +{ + private HazelcastConfigFactoryBean configFactory; + private Resource resource; + private Properties properties; + + @Before + public void setUp() throws Exception + { + configFactory = new HazelcastConfigFactoryBean(); + resource = new ClassPathResource("cluster-test/placeholder-test.xml"); + configFactory.setConfigFile(resource); + + properties = new Properties(); + properties.setProperty("alfresco.hazelcast.password", "let-me-in"); + properties.setProperty("alfresco.cluster.name", "cluster-name"); + configFactory.setProperties(properties); + + // Trigger the spring post-bean creation lifecycle method + configFactory.afterPropertiesSet(); + } + + + @Test + public void testConfigHasNewPropertyValues() throws Exception + { + // Invoke the factory method. + Config config = configFactory.getObject(); + + assertEquals("let-me-in", config.getGroupConfig().getPassword()); + assertEquals("cluster-name", config.getGroupConfig().getName()); + } +} diff --git a/source/java/org/alfresco/repo/cluster/HazelcastInstanceFactory.java b/source/java/org/alfresco/repo/cluster/HazelcastInstanceFactory.java new file mode 100644 index 0000000000..92652f389c --- /dev/null +++ b/source/java/org/alfresco/repo/cluster/HazelcastInstanceFactory.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2005-2013 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.cluster; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.alfresco.util.PropertyCheck; + +import com.hazelcast.config.Config; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; + +/** + * Provides a way of lazily creating HazelcastInstances for a given configuration. + * The HazelcastInstance will not be created until {@link #getInstance()} is called. + *

+ * An intermediary class such as this is required in order to avoid starting + * Hazelcast instances when clustering is not configured/required. Otherwise + * simply by defining a HazelcastInstance bean clustering would spring into life. + *

+ * Please note this class provides non-static access deliberately, and should be + * injected into any clients that require its services. + * + * @author Matt Ward + */ +public class HazelcastInstanceFactory +{ + private Config config; + private HazelcastInstance hazelcastInstance; + /** Guards {@link #config} and {@link #hazelcastInstance} */ + private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); + + public HazelcastInstance getInstance() + { + rwLock.readLock().lock(); + try + { + if (hazelcastInstance != null) + { + return hazelcastInstance; + } + } + finally + { + rwLock.readLock().unlock(); + } + + // hazelcastInstance is null, so create it. + rwLock.writeLock().lock(); + try + { + // Double check condition hasn't changed in between locks. + if (hazelcastInstance == null) + { + hazelcastInstance = Hazelcast.newHazelcastInstance(config); + } + return hazelcastInstance; + } + finally + { + rwLock.writeLock().unlock(); + } + } + + /** + * Checks whether hazelcast has been given a valid cluster name. If so, + * then clustering is considered enabled. This condition should be checked + * before calling {@link #getInstance()}. + * + * @return true if clustering is enabled, false otherwise. + */ + public boolean isClusteringEnabled() + { + rwLock.readLock().lock(); + try + { + String clusterName = config.getGroupConfig().getName(); + return (PropertyCheck.isValidPropertyString(clusterName)); + } + finally + { + rwLock.readLock().unlock(); + } + } + + /** + * Retrieve the name of the cluster for the configuration used by this factory. + * + * @return String - the cluster name. + */ + public String getClusterName() + { + rwLock.readLock().lock(); + try + { + String clusterName = config.getGroupConfig().getName(); + return clusterName; + } + finally + { + rwLock.readLock().unlock(); + } + } + + /** + * Sets the Hazelcast configuration that will be used by this factory when + * creating the HazelcastInstance. + * + * @param config Hazelcast configuration + */ + public void setConfig(Config config) + { + rwLock.writeLock().lock(); + try + { + this.config = config; + } + finally + { + rwLock.writeLock().unlock(); + } + } +} diff --git a/source/java/org/alfresco/repo/importer/ImporterComponent.java b/source/java/org/alfresco/repo/importer/ImporterComponent.java index a3cba3d057..88a7edb45f 100644 --- a/source/java/org/alfresco/repo/importer/ImporterComponent.java +++ b/source/java/org/alfresco/repo/importer/ImporterComponent.java @@ -775,6 +775,12 @@ public class ImporterComponent implements ImporterService String contentUrl = contentData.getContentUrl(); if (contentUrl != null && contentUrl.length() > 0) { + Map propsBefore = null; + if (contentUsageImpl != null && contentUsageImpl.getEnabled()) + { + propsBefore = nodeService.getProperties(nodeRef); + } + if (contentCache != null) { // import content from source @@ -783,27 +789,20 @@ public class ImporterComponent implements ImporterService } else { - // import the content from the url + // import the content from the import source file InputStream contentStream = streamHandler.importStream(contentUrl); ContentWriter writer = contentService.getWriter(nodeRef, propertyName, true); writer.setEncoding(contentData.getEncoding()); writer.setMimetype(contentData.getMimetype()); - - Map propsBefore = null; - if (contentUsageImpl != null && contentUsageImpl.getEnabled()) - { - propsBefore = nodeService.getProperties(nodeRef); - } - writer.putContent(contentStream); - - if (contentUsageImpl != null && contentUsageImpl.getEnabled()) - { - // Since behaviours for content nodes have all been disabled, - // it is necessary to update the user's usage stats. - Map propsAfter = nodeService.getProperties(nodeRef); - contentUsageImpl.onUpdateProperties(nodeRef, propsBefore, propsAfter); - } + } + + if (contentUsageImpl != null && contentUsageImpl.getEnabled()) + { + // Since behaviours for content nodes have all been disabled, + // it is necessary to update the user's usage stats. + Map propsAfter = nodeService.getProperties(nodeRef); + contentUsageImpl.onUpdateProperties(nodeRef, propsBefore, propsAfter); } reportContentCreated(nodeRef, contentUrl); diff --git a/source/java/org/alfresco/repo/mail/AlfrescoJavaMailSender.java b/source/java/org/alfresco/repo/mail/AlfrescoJavaMailSender.java index 074f47bf56..9857e4c222 100644 --- a/source/java/org/alfresco/repo/mail/AlfrescoJavaMailSender.java +++ b/source/java/org/alfresco/repo/mail/AlfrescoJavaMailSender.java @@ -25,7 +25,6 @@ import javax.mail.NoSuchProviderException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.URLName; -import javax.mail.event.ConnectionListener; import org.apache.commons.pool.KeyedPoolableObjectFactory; import org.apache.commons.pool.impl.GenericKeyedObjectPool; diff --git a/source/java/org/alfresco/repo/model/Repository.java b/source/java/org/alfresco/repo/model/Repository.java index eeb0b6adee..aefce2ce89 100644 --- a/source/java/org/alfresco/repo/model/Repository.java +++ b/source/java/org/alfresco/repo/model/Repository.java @@ -181,6 +181,7 @@ public class Repository implements ApplicationContextAware @Override protected void onShutdown(ApplicationEvent event) { + //NOOP } } @@ -204,7 +205,7 @@ public class Repository implements ApplicationContextAware } /** - * Gets the Company Home + * Gets the Company Home. Note this is tenant-aware if the correct Cache is supplied. * * @return company home node ref */ diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java index ff18920a17..920c4ee302 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java @@ -203,9 +203,8 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest nodeService.deleteNode(nodeWithFreeMarkerContent); nodeService.deleteNode(testTargetFolder); } - - //TODO Fix this failing test. - public void off_testRenderFreeMarkerTemplate() throws Exception + + public void testRenderFreeMarkerTemplate() throws Exception { this.setComplete(); this.endTransaction(); diff --git a/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java index 00d2598dd5..f53c9f338d 100644 --- a/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java +++ b/source/java/org/alfresco/repo/security/person/GetPeopleCannedQuery.java @@ -63,6 +63,8 @@ public class GetPeopleCannedQuery extends AbstractCannedQuery public static final int MAX_FILTER_SORT_PROPS = 3; + private static final int MAX_EXPECTED_ADMINS = 5; // TODO refine non-admin paging + private NodeDAO nodeDAO; private QNameDAO qnameDAO; private CannedQueryDAO cannedQueryDAO; @@ -190,26 +192,35 @@ public class GetPeopleCannedQuery extends AbstractCannedQuery filterSortPropCnt = setFilterSortParams(sortFilterProps, sortAsc, params); // filtered and/or sorted - note: permissions not applicable for getPeople - final List result = new ArrayList(100); + List result = new ArrayList(100); final PersonQueryCallback c = new DefaultPersonQueryCallback(result, paramBean.getIncludeAdministrators()); PersonResultHandler resultHandler = new PersonResultHandler(c); int offset = parameters.getPageDetails().getSkipResults(); int totalResultCountMax = parameters.getTotalResultCountMax(); - int limit = totalResultCountMax > 0 ? totalResultCountMax : parameters.getPageDetails().getPageSize(); - if (limit != Integer.MAX_VALUE) + + int origOffset = offset; + int origLimit = totalResultCountMax > 0 ? totalResultCountMax : parameters.getPageDetails().getPageSize(); + + long newLimit = (long)origLimit; + + // to enable hasMore flag + newLimit++; + + boolean excludeAdmins = (! paramBean.getIncludeAdministrators()); + if (excludeAdmins) { - // to enable hasMore flag - limit++; - - if ((! paramBean.getIncludeAdministrators()) && (limit != Integer.MAX_VALUE)) - { - // TODO - only works in case where there is only 1 default admin - limit++; - } + // TODO refine - non-admin paging + offset = 0; + newLimit = offset + (long)newLimit + MAX_EXPECTED_ADMINS; } - cannedQueryDAO.executeQuery(QUERY_NAMESPACE, QUERY_SELECT_GET_PEOPLE, params, offset, limit, resultHandler); + if (newLimit > Integer.MAX_VALUE) + { + newLimit = Integer.MAX_VALUE; + } + + cannedQueryDAO.executeQuery(QUERY_NAMESPACE, QUERY_SELECT_GET_PEOPLE, params, offset, (int)newLimit, resultHandler); resultHandler.done(); if (start != null) @@ -217,6 +228,17 @@ public class GetPeopleCannedQuery extends AbstractCannedQuery logger.debug("Base query: "+result.size()+" in "+(System.currentTimeMillis()-start)+" msecs"); } + if (excludeAdmins) + { + // TODO refine - non-admin paging + long max = origOffset + (long)origLimit; + if (max > result.size()) + { + max = result.size(); + } + result = result.subList(origOffset, (int)max); + } + return result; } @@ -296,7 +318,9 @@ public class GetPeopleCannedQuery extends AbstractCannedQuery @Override protected Pair getTotalResultCount(List results) { - return super.getTotalResultCount(results); + int offset = super.getParameters().getPageDetails().getSkipResults(); + Integer size = offset + results.size(); + return new Pair(size, size); } protected interface PersonQueryCallback @@ -318,9 +342,7 @@ public class GetPeopleCannedQuery extends AbstractCannedQuery @Override public boolean handle(NodeRef personRef) { - // TODO refine - // - return username as part of query - // - can break paging if more than one admin + // TODO refine - return username as part of query if (includeAdministrators == false) { String userName = (String) nodeService.getProperty(personRef, ContentModel.PROP_USERNAME); diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index ea129f18de..db502a7dea 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -430,14 +430,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per return getPerson(userName, true); } - /** - * {@inheritDoc} - */ - public NodeRef getPersonOrNull(String userName) - { - return getPersonImpl(userName, false, false); - } - /** * {@inheritDoc} */ @@ -453,9 +445,8 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per throw new NoSuchPersonException(personRef.toString()); } - // belts-and-braces String username = (String)props.get(ContentModel.PROP_USERNAME); - if (getPersonOrNull(username) == null) + if (username == null) { throw new NoSuchPersonException(personRef.toString()); } @@ -466,6 +457,14 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per (String)props.get(ContentModel.PROP_LASTNAME)); } + /** + * {@inheritDoc} + */ + public NodeRef getPersonOrNull(String userName) + { + return getPersonImpl(userName, false, false); + } + /** * {@inheritDoc} */ @@ -474,6 +473,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per return getPersonImpl(userName, autoCreateHomeFolderAndMissingPersonIfAllowed, true); } + private NodeRef getPersonImpl( final String userName, final boolean autoCreateHomeFolderAndMissingPersonIfAllowed, @@ -513,7 +513,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per */ public boolean personExists(String caseSensitiveUserName) { - NodeRef person = getPersonOrNull(caseSensitiveUserName); + NodeRef person = getPersonOrNullImpl(caseSensitiveUserName); if (person != null) { // re: THOR-293 diff --git a/source/java/org/alfresco/repo/security/person/PersonTest.java b/source/java/org/alfresco/repo/security/person/PersonTest.java index eec7733fc9..23a5c81e35 100644 --- a/source/java/org/alfresco/repo/security/person/PersonTest.java +++ b/source/java/org/alfresco/repo/security/person/PersonTest.java @@ -636,9 +636,10 @@ public class PersonTest extends TestCase NodeRef p6 = personService.createPerson(createDefaultProperties("bb", "Bb", "Bb", "bb@bb", "alfresco", rootNodeRef)); NodeRef p7 = personService.createPerson(createDefaultProperties("dd", "Dd", "Dd", "dd@dd", "alfresco", rootNodeRef)); + int expectedTotalCount = 7; + assertEquals(expectedTotalCount, getPeopleCount()); - - assertEquals(7, getPeopleCount()); + Pair expectedResultCount = new Pair(expectedTotalCount,expectedTotalCount); List> sort = new ArrayList>(1); sort.add(new Pair(ContentModel.PROP_USERNAME, true)); @@ -651,13 +652,16 @@ public class PersonTest extends TestCase assertEquals(p3, results.get(0).getNodeRef()); assertEquals(p1, results.get(1).getNodeRef()); - // page 2 + // page 2 (with total count) pr = new PagingRequest(2, 2, null); + pr.setRequestTotalCountMax(Integer.MAX_VALUE); + ppr = personService.getPeople(null, true, sort, pr); results = ppr.getPage(); assertEquals(2, results.size()); assertEquals(p6, results.get(0).getNodeRef()); assertEquals(p4, results.get(1).getNodeRef()); + assertEquals(expectedResultCount, ppr.getTotalResultCount()); // page 3 pr = new PagingRequest(4, 2, null); @@ -667,12 +671,76 @@ public class PersonTest extends TestCase assertEquals(p7, results.get(0).getNodeRef()); assertEquals(p2, results.get(1).getNodeRef()); - // page 4 + // page 4 (with total count) pr = new PagingRequest(6, 2, null); + pr.setRequestTotalCountMax(Integer.MAX_VALUE); + ppr = personService.getPeople(null, true, sort, pr); results = ppr.getPage(); assertEquals(1, results.size()); assertEquals(p5, results.get(0).getNodeRef()); + assertEquals(expectedResultCount, ppr.getTotalResultCount()); + } + + public void testPeopleSortingPaging_NoAdmin() + { + personService.setCreateMissingPeople(false); + + assertEquals(2, getPeopleCount()); + + NodeRef p1 = personService.getPerson(AuthenticationUtil.getAdminUserName()); // admin - by default + NodeRef p2 = personService.getPerson(AuthenticationUtil.getGuestUserName()); // guest - by default + + NodeRef p3 = personService.createPerson(createDefaultProperties("aa", "Aa", "Aa", "aa@aa", "alfresco", rootNodeRef)); + NodeRef p4 = personService.createPerson(createDefaultProperties("cc", "Cc", "Cc", "cc@cc", "alfresco", rootNodeRef)); + NodeRef p5 = personService.createPerson(createDefaultProperties("hh", "Hh", "Hh", "hh@hh", "alfresco", rootNodeRef)); + NodeRef p6 = personService.createPerson(createDefaultProperties("bb", "Bb", "Bb", "bb@bb", "alfresco", rootNodeRef)); + NodeRef p7 = personService.createPerson(createDefaultProperties("dd", "Dd", "Dd", "dd@dd", "alfresco", rootNodeRef)); + + int expectedTotalCount = 7; + assertEquals(expectedTotalCount, getPeopleCount()); + + int expectedTotalCountWithAdmin = expectedTotalCount - 1; + Pair expectedResultCount = new Pair(expectedTotalCountWithAdmin,expectedTotalCountWithAdmin); + + List> sort = new ArrayList>(1); + sort.add(new Pair(ContentModel.PROP_USERNAME, true)); + + // page 1 + PagingRequest pr = new PagingRequest(0, 2, null); + PagingResults ppr = personService.getPeople(null, null, null, null, false, sort, pr); + List results = ppr.getPage(); + assertEquals(2, results.size()); + assertEquals(p3, results.get(0).getNodeRef()); + assertEquals(p6, results.get(1).getNodeRef()); + + // page 2 (with total count) + pr = new PagingRequest(2, 2, null); + pr.setRequestTotalCountMax(Integer.MAX_VALUE); + + ppr = personService.getPeople(null, null, null, null, false, sort, pr); + results = ppr.getPage(); + assertEquals(2, results.size()); + assertEquals(p4, results.get(0).getNodeRef()); + assertEquals(p7, results.get(1).getNodeRef()); + assertEquals(expectedResultCount, ppr.getTotalResultCount()); + + // page 3 + pr = new PagingRequest(4, 2, null); + ppr = personService.getPeople(null, null, null, null, false, sort, pr); + results = ppr.getPage(); + assertEquals(2, results.size()); + assertEquals(p2, results.get(0).getNodeRef()); + assertEquals(p5, results.get(1).getNodeRef()); + + // page 4 (with total count) + pr = new PagingRequest(6, 2, null); + pr.setRequestTotalCountMax(Integer.MAX_VALUE); + + ppr = personService.getPeople(null, null, null, null, false, sort, pr); + results = ppr.getPage(); + assertEquals(0, results.size()); + assertEquals(expectedResultCount, ppr.getTotalResultCount()); } // note: this test can be removed as and when we remove the deprecated "getPeople" impl diff --git a/source/java/org/alfresco/repo/security/person/TestPersonManager.java b/source/java/org/alfresco/repo/security/person/TestPersonManager.java index c9699a3a86..c4256c478c 100644 --- a/source/java/org/alfresco/repo/security/person/TestPersonManager.java +++ b/source/java/org/alfresco/repo/security/person/TestPersonManager.java @@ -78,6 +78,15 @@ public class TestPersonManager } private NodeRef makePersonNode(String userName) + { + PropertyMap personProps = makePersonProperties(userName); + + NodeRef person = personService.createPerson(personProps); + people.put(userName, person); + return person; + } + + public static PropertyMap makePersonProperties(String userName) { PropertyMap personProps = new PropertyMap(); personProps.put(ContentModel.PROP_USERNAME, userName); @@ -86,10 +95,7 @@ public class TestPersonManager personProps.put(ContentModel.PROP_EMAIL, userName+EMAIL_SUFFIX); personProps.put(ContentModel.PROP_JOBTITLE, userName+JOB_SUFFIX); personProps.put(ContentModel.PROP_JOBTITLE, userName+ORGANISATION_SUFFIX); - - NodeRef person = personService.createPerson(personProps); - people.put(userName, person); - return person; + return personProps; } public NodeRef get(String userName) diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index 179f4ac776..aa87b9f98b 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -118,7 +118,7 @@ import org.springframework.extensions.surf.util.ParameterCheck; public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServiceInternal, SiteModel, NodeServicePolicies.OnRestoreNodePolicy { /** Logger */ - private static Log logger = LogFactory.getLog(SiteServiceImpl.class); + protected static Log logger = LogFactory.getLog(SiteServiceImpl.class); /** The DM store where site's are kept */ public static final StoreRef SITE_STORE = new StoreRef("workspace://SpacesStore"); @@ -938,8 +938,11 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic */ public List listSites(final String userName, final int size) { - // MT share - for activity service system callback - if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantUser(userName)) + // MT share - for activity service remote system callback (deprecated) + if (tenantService.isEnabled() && + TenantUtil.isCurrentDomainDefault() && + (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && + tenantService.isTenantUser(userName)) { final String tenantDomain = tenantService.getUserDomain(userName); @@ -1177,8 +1180,11 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic */ public SiteInfo getSite(final String shortName) { - // MT share - for activity service system callback - if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantName(shortName)) + // MT share - for activity service remote system callback (deprecated) + if (tenantService.isEnabled() && + TenantUtil.isCurrentDomainDefault() && + (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && + tenantService.isTenantName(shortName)) { final String tenantDomain = tenantService.getDomain(shortName); final String sName = tenantService.getBaseName(shortName, true); @@ -1715,8 +1721,11 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic public Map listMembers(String shortName, final String nameFilter, final String roleFilter, final int size, final boolean collapseGroups) { - // MT share - for activity service system callback - if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantName(shortName)) + // MT share - for activity service remote system callback (deprecated) + if (tenantService.isEnabled() && + TenantUtil.isCurrentDomainDefault() && + (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && + tenantService.isTenantName(shortName)) { final String tenantDomain = tenantService.getDomain(shortName); final String sName = tenantService.getBaseName(shortName, true); diff --git a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java index 30c342e7ce..c43b952a68 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTAdminServiceImpl.java @@ -579,10 +579,13 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo tenantDeployer.onEnableTenant(); } - // bootstrap workflows - for (WorkflowDeployer workflowDeployer : workflowDeployers) + // bootstrap workflows, if needed + if(workflowService.isMultiTenantWorkflowDeploymentEnabled()) { - workflowDeployer.init(); + for (WorkflowDeployer workflowDeployer : workflowDeployers) + { + workflowDeployer.init(); + } } // bootstrap modules (if any) @@ -773,14 +776,17 @@ public class MultiTAdminServiceImpl implements TenantAdminService, ApplicationCo { public Object doWork() { - List workflowDefs = workflowService.getDefinitions(); - if (workflowDefs != null) - { - for (WorkflowDefinition workflowDef : workflowDefs) - { - workflowService.undeployDefinition(workflowDef.getId()); - } - } + // Only undeploy tenant-workflows when MT-workflow deployment is enabled + if(workflowService.isMultiTenantWorkflowDeploymentEnabled()) { + List workflowDefs = workflowService.getDefinitions(); + if (workflowDefs != null) + { + for (WorkflowDefinition workflowDef : workflowDefs) + { + workflowService.undeployDefinition(workflowDef.getId()); + } + } + } List messageResourceBundles = repoAdminService.getMessageBundles(); if (messageResourceBundles != null) diff --git a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java index 246dc96243..03664211ec 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java +++ b/source/java/org/alfresco/repo/tenant/MultiTDemoTest.java @@ -222,7 +222,7 @@ public class MultiTDemoTest extends TestCase private void createTenant(final String tenantDomain) { // create tenants (if not already created) - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -236,7 +236,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, AuthenticationUtil.getSystemUserName()); + }, TenantService.DEFAULT_DOMAIN); } private void deleteTenant(final String tenantDomain) @@ -246,7 +246,7 @@ public class MultiTDemoTest extends TestCase public Object execute() throws Throwable { // delete tenant (if it exists) - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -259,7 +259,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, AuthenticationUtil.getSystemUserName()); + }, TenantService.DEFAULT_DOMAIN); return null; } }); @@ -270,7 +270,7 @@ public class MultiTDemoTest extends TestCase for (final String tenantDomain : tenants) { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -284,7 +284,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -374,8 +374,10 @@ public class MultiTDemoTest extends TestCase private void deleteTestAuthoritiesForTenant(final String[] uniqueGroupNames, final String userName) { + String tenantDomain = tenantService.getUserDomain(userName); + // Check deletion for tenant1 - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -391,12 +393,15 @@ public class MultiTDemoTest extends TestCase } return null; } - }, userName); + }, userName, tenantDomain); } + private void createTestAuthoritiesForTenant(final String[] uniqueGroupNames, final String userName) { + String tenantDomain = tenantService.getUserDomain(userName); + // Create groups for tenant - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -413,13 +418,15 @@ public class MultiTDemoTest extends TestCase return null; } - }, userName); + }, userName, tenantDomain); } - + private void checkTestAuthoritiesPresence(final String[] uniqueGroupNames, final String userName, final boolean shouldPresent) { + String tenantDomain = tenantService.getUserDomain(userName); + // Check that created permissions are not visible to tenant 2 - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -445,7 +452,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, userName); + }, userName, tenantDomain); } private void createGroup(String shortName, String parentShortName) @@ -924,7 +931,7 @@ public class MultiTDemoTest extends TestCase createTenant(tenantDomain1); String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain1); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -932,7 +939,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain1); createTenant(tenantDomain2); } @@ -979,7 +986,7 @@ public class MultiTDemoTest extends TestCase { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1009,7 +1016,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } catch (Throwable t) @@ -1101,20 +1108,20 @@ public class MultiTDemoTest extends TestCase assertTrue(tenants.size() > 0); - final int rootGrpsOrigCnt = TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + final int rootGrpsOrigCnt = TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Integer doWork() throws Exception { return authorityService.getAllRootAuthorities(AuthorityType.GROUP).size(); } - }, tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenants.get(0))); + }, tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenants.get(0)), tenants.get(0)); // create groups and add users for (final String tenantDomain : tenants) { final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1135,7 +1142,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } // check groups/users @@ -1143,7 +1150,7 @@ public class MultiTDemoTest extends TestCase { final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1172,7 +1179,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1181,7 +1188,7 @@ public class MultiTDemoTest extends TestCase logger.info("Create demo categories"); // super admin - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1190,13 +1197,12 @@ public class MultiTDemoTest extends TestCase return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName(), TenantService.DEFAULT_DOMAIN); for (final String tenantDomain : tenants) { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1206,7 +1212,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1304,7 +1310,7 @@ public class MultiTDemoTest extends TestCase { final String tenantUserName = tenantService.getDomainUser(baseUserName, tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1324,7 +1330,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantUserName); + }, tenantUserName, tenantDomain); } } } @@ -1347,7 +1353,7 @@ public class MultiTDemoTest extends TestCase { final String tenantUserName = tenantService.getDomainUser(baseUserName, tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1364,7 +1370,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantUserName); + }, tenantUserName, tenantDomain); } } } @@ -1375,7 +1381,7 @@ public class MultiTDemoTest extends TestCase logger.info("Get tenant stores"); // system - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsSystemTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1387,23 +1393,23 @@ public class MultiTDemoTest extends TestCase assertTrue("System: "+nodeService.getStores().size()+", "+(tenants.size()+1), (nodeService.getStores().size() >= (DEFAULT_STORE_COUNT * (tenants.size()+1)))); return null; } - }, AuthenticationUtil.getSystemUserName()); + }, TenantService.DEFAULT_DOMAIN); // super admin - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { assertTrue("Super admin: "+nodeService.getStores().size(), (nodeService.getStores().size() >= DEFAULT_STORE_COUNT)); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getAdminUserName(), TenantService.DEFAULT_DOMAIN); for (final String tenantDomain : tenants) { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1412,7 +1418,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1423,8 +1429,7 @@ public class MultiTDemoTest extends TestCase for (final String tenantDomain : tenants) { String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1445,7 +1450,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1456,8 +1461,7 @@ public class MultiTDemoTest extends TestCase for (final String tenantDomain : tenants) { final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1496,7 +1500,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1546,7 +1550,7 @@ public class MultiTDemoTest extends TestCase { final String tenantUserName = tenantService.getDomainUser(TEST_USER1, tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1554,7 +1558,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantUserName); + }, tenantUserName, tenantDomain); } } @@ -1600,8 +1604,7 @@ public class MultiTDemoTest extends TestCase for (final String tenantDomain : tenants) { final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1640,7 +1643,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1653,7 +1656,7 @@ public class MultiTDemoTest extends TestCase { final String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1667,7 +1670,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain); } } @@ -1681,7 +1684,7 @@ public class MultiTDemoTest extends TestCase { final String tenantUserName = tenantService.getDomainUser(TEST_USER1, tenantDomain); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1696,13 +1699,11 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantUserName); + }, tenantUserName, tenantDomain); } } - - // TODO pending CLOUD-1351 fix - public void xtest20_ALF_12732() + public void test20_ALF_12732() { final String tenantDomain1 = TEST_RUN+".one.alf12732"; @@ -1714,7 +1715,7 @@ public class MultiTDemoTest extends TestCase { AuthenticationUtil.setFullyAuthenticatedUser(tenantAdminName); // note: since SiteServiceImpl.setupSitePermissions currently uses getCurrentUserName (rather than runAs) - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1734,7 +1735,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain1); } finally { @@ -1751,7 +1752,8 @@ public class MultiTDemoTest extends TestCase createTenant(tenantDomain2); String tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain1); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1761,10 +1763,11 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain1); tenantAdminName = tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain2); - TenantUtil.runAsPrimaryTenant(new TenantRunAsWork() + + TenantUtil.runAsUserTenant(new TenantRunAsWork() { public Object doWork() throws Exception { @@ -1774,7 +1777,7 @@ public class MultiTDemoTest extends TestCase return null; } - }, tenantAdminName); + }, tenantAdminName, tenantDomain2); } diff --git a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java index 118be354d0..66266dcfeb 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java @@ -482,8 +482,8 @@ public class MultiTServiceImpl implements TenantService ParameterCheck.mandatory("RootPath", rootPath); ParameterCheck.mandatory("RootNodeRef", rootNodeRef); - String username = AuthenticationUtil.getFullyAuthenticatedUser(); - StoreRef storeRef = getName(username, rootNodeRef.getStoreRef()); + //String username = AuthenticationUtil.getFullyAuthenticatedUser(); + StoreRef storeRef = rootNodeRef.getStoreRef(); AuthenticationUtil.RunAsWork action = new GetRootNode(nodeService, searchService, namespaceService, rootPath, rootNodeRef, storeRef); return getBaseName(AuthenticationUtil.runAs(action, AuthenticationUtil.getSystemUserName())); diff --git a/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java b/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java index d8bd8b3656..d84fd631e2 100644 --- a/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java +++ b/source/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java @@ -124,11 +124,11 @@ public class TenantRoutingDataSource extends AbstractRoutingDataSource super.afterPropertiesSet(); // to update resolved data sources } - + @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { - return null; + throw new SQLFeatureNotSupportedException(); } } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java index a2017aa35f..3673b65a5f 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java @@ -244,6 +244,58 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi return result; } + public List getThumbnailDefinitions(String sourceUrl, String mimetype, long sourceSize) + { + List thumbnailDefinitionsLimitsForMimetype = this.mimetypeMap.get(mimetype); + + if (thumbnailDefinitionsLimitsForMimetype == null) + { + boolean foundAtLeastOneTransformer = false; + thumbnailDefinitionsLimitsForMimetype = new ArrayList(7); + + for (ThumbnailDefinition thumbnailDefinition : this.thumbnailDefinitions.values()) + { + if (isThumbnailDefinitionAvailable(sourceUrl, mimetype, sourceSize, thumbnailDefinition)) + { + long maxSourceSizeBytes = getMaxSourceSizeBytes(mimetype, thumbnailDefinition); + if (maxSourceSizeBytes != 0) + { + thumbnailDefinitionsLimitsForMimetype.add(new ThumbnailDefinitionLimits(thumbnailDefinition, maxSourceSizeBytes)); + foundAtLeastOneTransformer = true; + } + } + } + + // If we have found no transformers for the given MIME type then we do + // not cache the empty list. We prevent this because we want to allow for + // transformers only coming online *during* system operation - as opposed + // to coming online during startup. + // + // An example of such a transient transformer would be those that use OpenOffice.org. + // It is possible that the system might start without OOo-based transformers + // being available. Therefore we must not cache an empty list for the relevant + // MIME types - otherwise this class would hide the fact that OOo (soffice) has + // been launched and that new transformers are available. + if (foundAtLeastOneTransformer) + { + this.mimetypeMap.put(mimetype, thumbnailDefinitionsLimitsForMimetype); + } + } + + // Only return ThumbnailDefinition for this specific source - may be limited on size. + List result = new ArrayList(thumbnailDefinitionsLimitsForMimetype.size()); + for (ThumbnailDefinitionLimits thumbnailDefinitionLimits: thumbnailDefinitionsLimitsForMimetype) + { + long maxSourceSizeBytes = thumbnailDefinitionLimits.getMaxSourceSizeBytes(); + if (sourceSize <= 0 || maxSourceSizeBytes < 0 || maxSourceSizeBytes >= sourceSize) + { + result.add(thumbnailDefinitionLimits.getThumbnailDefinition()); + } + } + + return result; + } + /** * * @param mimetype @@ -290,6 +342,34 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi } } + /** + * Checks to see if at this moment in time, the specified {@link ThumbnailDefinition} + * is able to thumbnail the source mimetype. Typically used with Thumbnail Definitions + * retrieved by name, and/or when dealing with transient {@link ContentTransformer}s. + * @param sourceUrl The URL of the source (optional) + * @param sourceMimeType The source mimetype + * @param sourceSize the size (in bytes) of the source. Use -1 if unknown. + * @param thumbnailDefinition The {@link ThumbnailDefinition} to check for + */ + public boolean isThumbnailDefinitionAvailable(String sourceUrl, String sourceMimeType, long sourceSize, ThumbnailDefinition thumbnailDefinition) + { + // Log the following getTransform() as trace so we can see the wood for the trees + boolean orig = TransformerDebug.setDebugOutput(false); + try + { + return this.contentService.getTransformer( + sourceUrl, + sourceMimeType, + sourceSize, + thumbnailDefinition.getMimetype(), thumbnailDefinition.getTransformationOptions() + ) != null; + } + finally + { + TransformerDebug.setDebugOutput(orig); + } + } + /** * Returns the maximum source size of any content that may transformed between the supplied * sourceMimetype and thumbnailDefinition's targetMimetype using its transformation options. diff --git a/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java b/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java new file mode 100644 index 0000000000..351e406b7c --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/LockStoreFactoryImpl.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2005-2013 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.webdav; + +import java.util.concurrent.ConcurrentMap; + +import org.alfresco.repo.cluster.HazelcastInstanceFactory; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.PropertyCheck; + +import com.hazelcast.core.HazelcastInstance; + + +/** + * Default implementation of the {@link LockStoreFactory} interface. Creates {@link LockStore}s + * backed by a Hazelcast distributed Map if the clusterName property is set, + * otherwise it creates a non-clustered {@link SimpleLockStore}. + * + * @see LockStoreFactory + * @see LockStoreImpl + * @author Matt Ward + */ +public class LockStoreFactoryImpl implements LockStoreFactory +{ + private static final String HAZELCAST_MAP_NAME = "webdav-locks"; + private HazelcastInstanceFactory hazelcastInstanceFactory; + private String clusterName; + + /** + * This method should be used sparingly and the created {@link LockStore}s should be + * retained (this factory does not cache instances of them). + */ + @Override + public synchronized LockStore createLockStore() + { + if (!PropertyCheck.isValidPropertyString(clusterName)) + { + return new SimpleLockStore(); + } + else + { + HazelcastInstance instance = hazelcastInstanceFactory.getInstance(); + ConcurrentMap map = instance.getMap(HAZELCAST_MAP_NAME); + return new LockStoreImpl(map); + } + } + + /** + * @param hazelcastInstanceFactory the factory that will create a HazelcastInstance if required. + */ + public synchronized void setHazelcastInstanceFactory(HazelcastInstanceFactory hazelcastInstanceFactory) + { + this.hazelcastInstanceFactory = hazelcastInstanceFactory; + } + + /** + * @param clusterName the clusterName to set + */ + public synchronized void setClusterName(String clusterName) + { + this.clusterName = clusterName; + } +} diff --git a/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java b/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java index eb8ff584df..42fbd54af0 100644 --- a/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java +++ b/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java @@ -31,8 +31,6 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.webdav.WebDavService; import org.alfresco.service.namespace.QName; import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.extensions.surf.util.URLEncoder; /** @@ -46,8 +44,7 @@ import org.springframework.extensions.surf.util.URLEncoder; */ public class WebDavServiceImpl implements WebDavService { - public static final String WEBDAV_PREFIX = "webdav"; - private static Log logger = LogFactory.getLog(WebDavServiceImpl.class); + public static final String WEBDAV_PREFIX = "webdav"; private boolean enabled = false; private NodeService nodeService; private DictionaryService dictionaryService; diff --git a/source/java/org/alfresco/repo/workflow/AbstractMultitenantWorkflowTest.java b/source/java/org/alfresco/repo/workflow/AbstractMultitenantWorkflowTest.java index 999963b18a..a1d78440d3 100644 --- a/source/java/org/alfresco/repo/workflow/AbstractMultitenantWorkflowTest.java +++ b/source/java/org/alfresco/repo/workflow/AbstractMultitenantWorkflowTest.java @@ -29,6 +29,7 @@ import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantContextHolder; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; @@ -81,6 +82,7 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest { // Run as User1 so tenant domain 1 AuthenticationUtil.setFullyAuthenticatedUser(user1); + TenantContextHolder.setTenantDomain(tenant1); List allDefs = workflowService.getAllDefinitions(); int allDefsSize = allDefs.size(); @@ -104,6 +106,7 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest // Switch to tenant2. AuthenticationUtil.setFullyAuthenticatedUser(user2); + TenantContextHolder.setTenantDomain(tenant2); // Check definition not visible on tenant2. try @@ -125,13 +128,14 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest // Switch back to tenant1. AuthenticationUtil.setFullyAuthenticatedUser(user1); + TenantContextHolder.setTenantDomain(tenant1); // Check the definition hasn't changed WorkflowDefinition definitionByName = workflowService.getDefinitionByName(definitionKey); assertEquals(definition.getId(), definitionByName.getId()); } - public void xtestQueryTasks() throws Exception + public void testQueryTasks() throws Exception { WorkflowTaskQuery query = new WorkflowTaskQuery(); @@ -139,6 +143,7 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest // Run as User1 so tenant domain 1 AuthenticationUtil.setFullyAuthenticatedUser(user1); + TenantContextHolder.setTenantDomain(tenant1); // Check no tasks to start with List tasks = workflowService.queryTasks(query); @@ -168,6 +173,7 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest //Switch to tenant2 AuthenticationUtil.setFullyAuthenticatedUser(user2); + TenantContextHolder.setTenantDomain(tenant2); // Tenant2 should not find the task tasks = workflowService.queryTasks(query); @@ -224,7 +230,6 @@ public abstract class AbstractMultitenantWorkflowTest extends BaseSpringTest { if (! tenantAdminService.existsTenant(tenantDomain)) { - //tenantAdminService.createTenant(tenantDomain, DEFAULT_ADMIN_PW.toCharArray(), ROOT_DIR + "/" + tenantDomain); tenantAdminService.createTenant(tenantDomain, (DEFAULT_ADMIN_PW+" "+tenantDomain).toCharArray(), null); // use default root dir } return tenantService.getDomainUser(AuthenticationUtil.getAdminUserName(), tenantDomain); diff --git a/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java b/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java index cd9b9a61eb..92ee9a05c0 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java @@ -27,8 +27,8 @@ import java.util.Map; import org.alfresco.repo.i18n.MessageService; import org.alfresco.repo.jscript.ScriptNode; -import org.alfresco.repo.tenant.MultiTServiceImpl; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; @@ -370,10 +370,18 @@ public class WorkflowObjectFactory { processKey = getLocalEngineId(defName); } - tenantService.checkDomain(processKey); + if(tenantService.isTenantName(processKey)) + { + tenantService.checkDomain(processKey); + } } } + public boolean isDefaultDomain() + { + return TenantService.DEFAULT_DOMAIN.equals(tenantService.getCurrentUserDomain()); + } + public List filterByDomain(Collection values, final Function processKeyGetter) { final String currentDomain = tenantService.getCurrentUserDomain(); @@ -382,8 +390,8 @@ public class WorkflowObjectFactory public Boolean apply(T value) { String key = processKeyGetter.apply(value); - String domain = MultiTServiceImpl.getMultiTenantDomainName(key); - return (currentDomain.equals(domain)); + String domain = TenantUtil.getTenantDomain(key); + return currentDomain.equals(domain) || domain.equals(TenantService.DEFAULT_DOMAIN); } }); } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index 6448ca8b91..7e273f7404 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -89,6 +89,7 @@ public class WorkflowServiceImpl implements WorkflowService private WorkflowAdminService workflowAdminService; private int maxAuthoritiesForPooledTasks = 100; private int maxPooledTasks = -1; + private boolean deployWorkflowsInTenant = false; /** * Sets the Authority Service @@ -591,6 +592,16 @@ public class WorkflowServiceImpl implements WorkflowService } return result; } + + public void setMultiTenantWorkflowDeploymentEnabled(boolean deployWorkflowsInTenant) + { + this.deployWorkflowsInTenant = deployWorkflowsInTenant; + } + + public boolean isMultiTenantWorkflowDeploymentEnabled() + { + return deployWorkflowsInTenant; + } private Map> batchByEngineId(List workflowIds) { diff --git a/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java b/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java index 89cae5a23b..844525efc9 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowTestSuite.java @@ -69,6 +69,7 @@ public class WorkflowTestSuite extends TestSuite // These tests use a different Spring config. suite.addTestSuite( ActivitiMultitenantWorkflowTest.class ); + suite.addTestSuite( JbpmMultitenantWorkflowTest.class ); // Note the following workflow tests are not included in this sutie: diff --git a/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java b/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java index 4418d51542..35bf83af86 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java +++ b/source/java/org/alfresco/repo/workflow/activiti/AbstractActivitiComponentTest.java @@ -44,9 +44,9 @@ import org.activiti.engine.runtime.ProcessInstance; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authority.AuthorityDAO; -import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.repo.workflow.activiti.variable.ScriptNodeVariableType; import org.alfresco.service.ServiceRegistry; @@ -123,9 +123,6 @@ public class AbstractActivitiComponentTest @Resource protected MessageService messageService; - @Resource - protected TenantService tenantService; - @Resource(name="NamespaceService") protected NamespaceService namespaceService; @@ -203,7 +200,6 @@ public class AbstractActivitiComponentTest @Before public void setUp() throws Exception { - mockTenantService(); mockNamespaceService(); mockDictionaryService(); mockNodeService(); @@ -213,8 +209,9 @@ public class AbstractActivitiComponentTest mockAuthorityDAO(); mockServiceRegistry(); - workflowEngine.setCompanyHomeStore("workspace://SpacesStore"); - workflowEngine.setCompanyHomePath("spaces.company_home.childname"); + Repository repoHelper = mock(Repository.class); + when(repoHelper.getCompanyHome()).thenReturn(companyHomeNode); + workflowEngine.setRepositoryHelper(repoHelper); // Also add custom type // TODO: Should come from configuration @@ -379,29 +376,6 @@ public class AbstractActivitiComponentTest namespaceService.registerNamespace("test", "http://test"); } - private void mockTenantService() - { - when(tenantService.getBaseName(anyString())).thenAnswer(new Answer() - { - public String answer(InvocationOnMock invocation) throws Throwable - { - Object arg= invocation.getArguments()[0]; - return (String) arg; - } - }); - - when(tenantService.getName(anyString())).thenAnswer(new Answer() - { - public String answer(InvocationOnMock invocation) throws Throwable - { - Object arg= invocation.getArguments()[0]; - return (String) arg; - } - }); - - when(tenantService.getCurrentUserDomain()).thenReturn(""); - } - @After public void tearDown() { diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java index a651d7dd52..c66e31a915 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiConstants.java @@ -35,7 +35,6 @@ public interface ActivitiConstants public static final String DEFAULT_TRANSITION_NAME = "Next"; public static final String DEFAULT_TRANSITION_DESCRIPTION = "Default Transition"; - public static final String START_TASK_PROPERTY_PREFIX = "_start_"; public static final String USER_TASK_NODE_TYPE = "userTask"; public static final String PROP_TASK_FORM_KEY = "taskFormKey"; @@ -46,4 +45,7 @@ public interface ActivitiConstants public static final String SERVICE_REGISTRY_BEAN_KEY = "services"; public static final String PROCESS_INSTANCE_IMAGE_FORMAT = "png"; + + public static final String VAR_TENANT_DOMAIN = "_tenant_domain"; + } diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiEngineInitializer.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiEngineInitializer.java index 5c0a115c2d..3d549ec8c9 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiEngineInitializer.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiEngineInitializer.java @@ -20,41 +20,44 @@ package org.alfresco.repo.workflow.activiti; import org.activiti.engine.ProcessEngine; import org.activiti.engine.impl.ProcessEngineImpl; -import org.alfresco.repo.domain.schema.SchemaAvailableEvent; import org.alfresco.service.cmr.workflow.WorkflowAdminService; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationListener; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; /** - * Class that waits for an {@link SchemaAvailableEvent} to start the activiti - * job executor. + * Bean that starts up the Activiti job executor as part of the + * bootstrap process. * * @author Frederik Heremans * @since 4.0 */ -public class ActivitiEngineInitializer implements ApplicationListener +public class ActivitiEngineInitializer extends AbstractLifecycleBean { private ProcessEngine processEngine; private WorkflowAdminService workflowAdminService; - public void setProcessEngine(ProcessEngine processEngine) - { - this.processEngine = processEngine; - } - public void setWorkflowAdminService(WorkflowAdminService workflowAdminService) { this.workflowAdminService = workflowAdminService; } @Override - public void onApplicationEvent(ApplicationEvent event) - { - if (event instanceof SchemaAvailableEvent && processEngine instanceof ProcessEngineImpl && - workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID)) - { - // Start the job-executor - ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getJobExecutor().start(); - } + protected void onBootstrap(ApplicationEvent event) { + + this.processEngine = getApplicationContext().getBean(ProcessEngine.class); + + if (workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID)) + { + ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getJobExecutor().start(); + } } + + @Override + protected void onShutdown(ApplicationEvent event) { + if(workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID) && + ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getJobExecutor().isActive()) + { + ((ProcessEngineImpl)processEngine).getProcessEngineConfiguration().getJobExecutor().shutdown(); + } + } } diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiMultitenantWorkflowTest.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiMultitenantWorkflowTest.java index c568644097..919c071abf 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiMultitenantWorkflowTest.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiMultitenantWorkflowTest.java @@ -29,6 +29,7 @@ import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.namespace.QName; + /** * @author Nick Smith * @since 4.0 diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java index 08645b77cc..dcf7a97c19 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTypeConverter.java @@ -19,6 +19,9 @@ package org.alfresco.repo.workflow.activiti; +import static org.alfresco.repo.workflow.activiti.ActivitiConstants.DEFAULT_TRANSITION_DESCRIPTION; +import static org.alfresco.repo.workflow.activiti.ActivitiConstants.DEFAULT_TRANSITION_NAME; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -30,12 +33,9 @@ import java.util.List; import java.util.Map; import java.util.Set; -import static org.alfresco.repo.workflow.activiti.ActivitiConstants.DEFAULT_TRANSITION_NAME; -import static org.alfresco.repo.workflow.activiti.ActivitiConstants.DEFAULT_TRANSITION_DESCRIPTION; import org.activiti.engine.FormService; import org.activiti.engine.HistoryService; import org.activiti.engine.ProcessEngine; -import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.form.StartFormData; import org.activiti.engine.form.TaskFormData; @@ -56,6 +56,7 @@ import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.Execution; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.workflow.WorkflowObjectFactory; import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; import org.alfresco.service.cmr.workflow.WorkflowDefinition; @@ -81,8 +82,6 @@ public class ActivitiTypeConverter private static final String TRANSITION_SUFFIX= ".transition"; private static final String DEFAULT_TRANSITION_KEY= "bpm_businessprocessmodel.transition"; - - private final RepositoryService repoService; private final RuntimeService runtimeService; private final FormService formService; private final HistoryService historyService; @@ -93,15 +92,14 @@ public class ActivitiTypeConverter public ActivitiTypeConverter(ProcessEngine processEngine, WorkflowObjectFactory factory, - ActivitiPropertyConverter propertyConverter) + ActivitiPropertyConverter propertyConverter, boolean deployWorkflowsInTenant) { - this.repoService = processEngine.getRepositoryService(); this.runtimeService = processEngine.getRuntimeService(); this.formService = processEngine.getFormService(); this.historyService = processEngine.getHistoryService(); this.factory = factory; this.propertyConverter =propertyConverter; - this.activitiUtil = new ActivitiUtil(processEngine); + this.activitiUtil = new ActivitiUtil(processEngine, deployWorkflowsInTenant); } public List filterByDomainAndConvert(List values, Function processKeyGetter) @@ -120,10 +118,7 @@ public class ActivitiTypeConverter if(deployment == null) return null; - List processDefs = repoService.createProcessDefinitionQuery() - .deploymentId(deployment.getId()) - .list(); - ProcessDefinition processDef = processDefs.get(0); + ProcessDefinition processDef = activitiUtil.getProcessDefinitionForDeployment(deployment.getId()); WorkflowDefinition wfDef = convert(processDef); return factory.createDeployment(wfDef); } @@ -144,7 +139,7 @@ public class ActivitiTypeConverter String defaultTitle = definition.getName(); String startTaskName = null; - StartFormData startFormData = formService.getStartFormData(definition.getId()); + StartFormData startFormData = getStartFormData(defId, defName); if(startFormData != null) { startTaskName = startFormData.getFormKey(); @@ -158,6 +153,11 @@ public class ActivitiTypeConverter defName, version, defaultTitle, null, startTask); } + + private StartFormData getStartFormData(final String definitionId, String processKey) + { + return formService.getStartFormData(definitionId); + } public WorkflowTaskDefinition getTaskDefinition(PvmActivity activity, String taskFormKey, String processKey, boolean isStart) { @@ -495,6 +495,12 @@ public class ActivitiTypeConverter private WorkflowTask getVirtualStartTask(ProcessInstance processInstance, boolean inProgress) { String processInstanceId = processInstance.getId(); + + if (!activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && !isCorrectTenantRuntime(processInstanceId)) + { + return null; + } + String id = ActivitiConstants.START_TASK_PREFIX + processInstanceId; WorkflowTaskState state = null; @@ -510,10 +516,12 @@ public class ActivitiTypeConverter WorkflowPath path = convert((Execution)processInstance); // Convert start-event to start-task Node - ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(processInstance.getProcessDefinitionId()); + String definitionId = processInstance.getProcessDefinitionId(); + ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(definitionId); WorkflowNode startNode = convert(procDef.getInitial(), true); - StartFormData startFormData = formService.getStartFormData(processInstance.getProcessDefinitionId()); + String key = ((ProcessDefinition)procDef).getKey(); + StartFormData startFormData = getStartFormData(definitionId, key); String taskDefId = null; if(startFormData != null) { @@ -524,7 +532,7 @@ public class ActivitiTypeConverter // Add properties based on HistoricProcessInstance HistoricProcessInstance historicProcessInstance = historyService .createHistoricProcessInstanceQuery() - .processInstanceId(processInstance.getId()) + .processInstanceId(processInstanceId) .singleResult(); Map properties = propertyConverter.getStartTaskProperties(historicProcessInstance, taskDefId, !inProgress); @@ -536,6 +544,38 @@ public class ActivitiTypeConverter return factory.createTask(id, taskDef, taskDef.getId(), defaultTitle, defaultDescription, state, path, properties); } + + public boolean isCorrectTenantRuntime(String processInstanceId, boolean isRuntime) + { + // Runtime domain only applicable in case tenant-aware deployment is turned off + if(!activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) { + if (isRuntime) + { + return isCorrectTenantRuntime(processInstanceId); + } + else + { + return isCorrectTenantHistoric(processInstanceId); + } + } + return true; + } + + public boolean isCorrectTenantRuntime(String processInstanceId) + { + return runtimeService.createProcessInstanceQuery() + .processInstanceId(processInstanceId) + .variableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()) + .count()>0; + } + + public boolean isCorrectTenantHistoric(String processInstanceId) + { + return historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .variableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()) + .count()>0; + } private WorkflowTask getVirtualStartTask(HistoricProcessInstance historicProcessInstance) { @@ -543,8 +583,13 @@ public class ActivitiTypeConverter { return null; } - String processInstanceId = historicProcessInstance.getId(); + + if (!activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && false == isCorrectTenantHistoric(processInstanceId)) + { + return null; + } + String id = ActivitiConstants.START_TASK_PREFIX + processInstanceId; // Since the process instance is complete the Start Task must be complete! diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java index a4b31809c2..7b87a4b144 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiUtil.java @@ -31,6 +31,7 @@ import org.activiti.engine.TaskService; import org.activiti.engine.form.StartFormData; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.history.HistoricTaskInstance; +import org.activiti.engine.history.HistoricTaskInstanceQuery; import org.activiti.engine.impl.RepositoryServiceImpl; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition; @@ -38,6 +39,8 @@ import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.Execution; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; +import org.activiti.engine.task.TaskQuery; +import org.alfresco.repo.tenant.TenantUtil; /** * @author Nick Smith @@ -51,8 +54,9 @@ public class ActivitiUtil private final TaskService taskService; private final FormService formService; private final ManagementService managementService; + private boolean deployWorkflowsInTenant; - public ActivitiUtil(ProcessEngine engine) + public ActivitiUtil(ProcessEngine engine, boolean deployWorkflowsInTenant) { this.repoService = engine.getRepositoryService(); this.runtimeService = engine.getRuntimeService(); @@ -60,16 +64,31 @@ public class ActivitiUtil this.historyService = engine.getHistoryService(); this.formService = engine.getFormService(); this.managementService = engine.getManagementService(); + this.deployWorkflowsInTenant = deployWorkflowsInTenant; } public ProcessDefinition getProcessDefinition(String definitionId) { - ProcessDefinition procDef = repoService.createProcessDefinitionQuery() + return repoService.createProcessDefinitionQuery() .processDefinitionId(definitionId) .singleResult(); - return procDef; } + public ProcessDefinition getProcessDefinitionByKey(String processKey) + { + return repoService.createProcessDefinitionQuery() + .processDefinitionKey(processKey) + .latestVersion() + .singleResult(); + } + + public ProcessDefinition getProcessDefinitionForDeployment(String deploymentId) + { + return repoService.createProcessDefinitionQuery() + .deploymentId(deploymentId) + .singleResult(); + } + public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() @@ -79,7 +98,11 @@ public class ActivitiUtil public Task getTaskInstance(String taskId) { - return taskService.createTaskQuery().taskId(taskId).singleResult(); + TaskQuery taskQuery = taskService.createTaskQuery().taskId(taskId); + if(!deployWorkflowsInTenant) { + taskQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + return taskQuery.singleResult(); } public HistoricProcessInstance getHistoricProcessInstance(String id) @@ -183,6 +206,16 @@ public class ActivitiUtil */ public HistoricTaskInstance getHistoricTaskInstance(String localId) { - return historyService.createHistoricTaskInstanceQuery().taskId(localId).singleResult(); + HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery() + .taskId(localId); + if(!deployWorkflowsInTenant) { + taskQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + return taskQuery.singleResult(); } + + public boolean isMultiTenantWorkflowDeploymentEnabled() + { + return deployWorkflowsInTenant; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java index f088443b2e..219861b1e5 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowEngine.java @@ -59,6 +59,7 @@ import org.activiti.engine.impl.pvm.ReadOnlyProcessDefinition; import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.repository.ProcessDefinitionQuery; import org.activiti.engine.runtime.Execution; import org.activiti.engine.runtime.Job; import org.activiti.engine.runtime.ProcessInstance; @@ -66,8 +67,10 @@ import org.activiti.engine.task.Task; import org.activiti.engine.task.TaskQuery; import org.alfresco.model.ContentModel; import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.workflow.BPMEngine; import org.alfresco.repo.workflow.WorkflowAuthorityManager; import org.alfresco.repo.workflow.WorkflowConstants; @@ -81,9 +84,7 @@ import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; 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.search.SearchService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.LazyActivitiWorkflowTask; import org.alfresco.service.cmr.workflow.WorkflowDefinition; @@ -130,54 +131,35 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine private static final String ERR_GET_ALL_DEFS_BY_NAME = "activiti.engine.get.all.workflow.definitions.by.name.error"; private static final String ERR_GET_DEF_IMAGE = "activiti.engine.get.workflow.definition.image.error"; private static final String ERR_GET_DEF_UNEXISTING_IMAGE = "activiti.engine.get.workflow.definition.unexisting.image.error"; -// private static final String ERR_GET_TASK_DEFS = "activiti.engine.get.task.definitions.error"; -// private static final String ERR_GET_PROCESS_DEF = "activiti.engine.get.process.definition.error"; private static final String ERR_START_WORKFLOW = "activiti.engine.start.workflow.error"; private static final String ERR_GET_WORKFLOW_INSTS = "activiti.engine.get.workflows.error"; private static final String ERR_GET_ACTIVE_WORKFLOW_INSTS = "activiti.engine.get.active.workflows.error"; private static final String ERR_GET_COMPLETED_WORKFLOW_INSTS = "activiti.engine.get.completed.workflows.error"; -// private static final String ERR_GET_WORKFLOW_INST_BY_ID = "activiti.engine.get.workflow.instance.by.id.error"; -// private static final String ERR_GET_PROCESS_INSTANCE = "activiti.engine.get.process.instance.error"; private static final String ERR_GET_WORKFLOW_PATHS = "activiti.engine.get.workflow.paths.error"; -// private static final String ERR_GET_PATH_PROPERTIES = "activiti.engine.get.path.properties.error"; private static final String ERR_CANCEL_WORKFLOW = "activiti.engine.cancel.workflow.error"; private static final String ERR_CANCEL_UNEXISTING_WORKFLOW = "activiti.engine.cancel.unexisting.workflow.error"; private static final String ERR_DELETE_WORKFLOW = "activiti.engine.delete.workflow.error"; private static final String ERR_DELETE_UNEXISTING_WORKFLOW = "activiti.engine.delete.unexisting.workflow.error"; -// private static final String ERR_SIGNAL_TRANSITION = "activiti.engine.signal.transition.error"; protected static final String ERR_FIRE_EVENT_NOT_SUPPORTED = "activiti.engine.event.unsupported"; -// private static final String ERR_FIRE_EVENT = "activiti.engine.fire.event.error"; private static final String ERR_GET_TASKS_FOR_PATH = "activiti.engine.get.tasks.for.path.error"; -// private static final String ERR_GET_TASKS_FOR_UNEXISTING_PATH = "activiti.engine.get.tasks.for.unexisting.path.error"; private static final String ERR_GET_TIMERS = "activiti.engine.get.timers.error"; protected static final String ERR_FIND_COMPLETED_TASK_INSTS = "activiti.engine.find.completed.task.instances.error"; -// private static final String ERR_COMPILE_PROCESS_DEF_zip = "activiti.engine.compile.process.definition.zip.error"; -// private static final String ERR_COMPILE_PROCESS_DEF_XML = "activiti.engine.compile.process.definition.xml.error"; -// private static final String ERR_COMPILE_PROCESS_DEF_UNSUPPORTED = "activiti.engine.compile.process.definition.unsupported.error"; -// private static final String ERR_GET_activiti_ID = "activiti.engine.get.activiti.id.error"; private static final String ERR_GET_WORKFLOW_TOKEN_INVALID = "activiti.engine.get.workflow.token.invalid"; private static final String ERR_GET_WORKFLOW_TOKEN_NULL = "activiti.engine.get.workflow.token.is.null"; - private static final String ERR_GET_COMPANY_HOME_INVALID = "activiti.engine.get.company.home.invalid"; - private static final String ERR_GET_COMPANY_HOME_MULTIPLE = "activiti.engine.get.company.home.multiple"; // Task Component Messages private static final String ERR_GET_ASSIGNED_TASKS = "activiti.engine.get.assigned.tasks.error"; private static final String ERR_GET_POOLED_TASKS = "activiti.engine.get.pooled.tasks.error"; -// private static final String ERR_QUERY_TASKS = "activiti.engine.query.tasks.error"; -// private static final String ERR_GET_TASK_INST = "activiti.engine.get.task.instance.error"; private static final String ERR_UPDATE_TASK = "activiti.engine.update.task.error"; private static final String ERR_UPDATE_TASK_UNEXISTING = "activiti.engine.update.task.unexisting.error"; private static final String ERR_UPDATE_START_TASK = "activiti.engine.update.starttask.illegal.error"; -// private static final String ERR_END_TASK = "activiti.engine.end.task.error"; private static final String ERR_END_UNEXISTING_TASK = "activiti.engine.end.task.unexisting.error"; private static final String ERR_GET_TASK_BY_ID = "activiti.engine.get.task.by.id.error"; private static final String ERR_END_TASK_INVALID_TRANSITION = "activiti.engine.end.task.invalid.transition"; + + public static final QName QNAME_INITIATOR = QName.createQName(NamespaceService.DEFAULT_URI, WorkflowConstants.PROP_INITIATOR); - private final static String WORKFLOW_TOKEN_SEPERATOR = "\\$"; - - - private RepositoryService repoService; private RuntimeService runtimeService; private TaskService taskService; @@ -188,7 +170,6 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine private DictionaryService dictionaryService; private NodeService nodeService; - private SearchService unprotectedSearchService; private PersonService personService; private ActivitiTypeConverter typeConverter; private ActivitiPropertyConverter propertyConverter; @@ -199,10 +180,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine private MessageService messageService; private TenantService tenantService; private NamespaceService namespaceService; - - private String companyHomePath; - private StoreRef companyHomeStore; - + private Repository repositoryHelper; public ActivitiWorkflowEngine() { @@ -330,11 +308,6 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { String resourceName = GUID.generate() + BpmnDeployer.BPMN_RESOURCE_SUFFIXES[0]; - if(tenantService.isEnabled()) - { - name = tenantService.getName(name); - } - Deployment deployment = repoService.createDeployment() .addInputStream(resourceName, workflowDefinition) .name(name) @@ -344,7 +317,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine // not exposed return typeConverter.convert(deployment); } - catch(ActivitiException ae) + catch(Exception ae) { String msg = messageService.getMessage(ERR_DEPLOY_WORKFLOW); throw new WorkflowException(msg, ae); @@ -433,8 +406,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { try { - List definitions = repoService.createProcessDefinitionQuery().list(); - return getValidWorkflowDefinitions(definitions); + ProcessDefinitionQuery query = repoService.createProcessDefinitionQuery(); + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && !TenantUtil.isCurrentDomainDefault()) + { + query.processDefinitionKeyLike("@" + TenantUtil.getCurrentDomain() + "%"); + } + return getValidWorkflowDefinitions(query.list()); } catch (ActivitiException ae) { @@ -454,7 +431,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine List definitions = repoService.createProcessDefinitionQuery() .processDefinitionKey(key) .list(); - return typeConverter.convert(definitions); + return getValidWorkflowDefinitions(definitions); } catch (ActivitiException ae) { @@ -490,6 +467,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine ProcessDefinition procDef = repoService.createProcessDefinitionQuery() .processDefinitionId(localId ) .singleResult(); + + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && procDef != null) + { + factory.checkDomain(procDef.getKey()); + } + return typeConverter.convert(procDef); } catch (ActivitiException ae) @@ -506,11 +489,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { try { - String key =factory.getDomainProcessKey(workflowName); - ProcessDefinition definition = repoService.createProcessDefinitionQuery() - .processDefinitionKey(key) - .latestVersion() - .singleResult(); + String key = factory.getLocalEngineId(workflowName); + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + key = factory.getDomainProcessKey(workflowName); + } + ProcessDefinition definition = activitiUtil.getProcessDefinitionByKey(key); return typeConverter.convert(definition); } catch (ActivitiException ae) @@ -568,10 +552,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { try { - List definitions = repoService.createProcessDefinitionQuery() - .latestVersion() - .list(); - return getValidWorkflowDefinitions(definitions); + ProcessDefinitionQuery query = repoService.createProcessDefinitionQuery().latestVersion(); + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && !TenantUtil.isCurrentDomainDefault()) + { + query.processDefinitionKeyLike("@" + TenantUtil.getCurrentDomain() + "%"); + } + return getValidWorkflowDefinitions(query.list()); } catch (ActivitiException ae) { @@ -657,7 +643,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine try { // Extract the Activiti ID from the path - String executionId = getExecutionIdFromPath(pathId); + String executionId = createLocalId(pathId); if (executionId == null) { throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_INVALID, pathId)); @@ -670,11 +656,15 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_NULL, pathId)); } + String processInstanceId = execution.getProcessInstanceId(); + ArrayList resultList = new ArrayList(); + if(!activitiUtil.isMultiTenantWorkflowDeploymentEnabled() && false == typeConverter.isCorrectTenantRuntime(processInstanceId)) + { + return resultList; //Wrong tenant + } // Check if workflow's start task has been completed. If not, return // the virtual task // Otherwise, just return the runtime Activiti tasks - String processInstanceId = execution.getProcessInstanceId(); - ArrayList resultList = new ArrayList(); if (typeConverter.isStartTaskActive(processInstanceId)) { resultList.add(typeConverter.getVirtualStartTask(processInstanceId, true)); @@ -696,19 +686,6 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } } - protected String getExecutionIdFromPath(String workflowPath) - { - if(workflowPath != null) - { - String[] parts = workflowPath.split(WORKFLOW_TOKEN_SEPERATOR); - if(parts.length != 2) - { - throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_INVALID, workflowPath)); - } - return parts[1]; - } - return null; - } /** * {@inheritDoc} @@ -867,24 +844,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine */ public boolean isDefinitionDeployed(InputStream workflowDefinition, String mimetype) { - String key = null; try { - key = getProcessKey(workflowDefinition); + String key = getProcessKey(workflowDefinition); + return null != activitiUtil.getProcessDefinitionByKey(key); } - catch (Exception e) - { - throw new WorkflowException(e.getMessage(), e); - } - - try - { - long count = repoService.createProcessDefinitionQuery() - .processDefinitionKey(key ) - .count(); - return count>0; - } - catch (ActivitiException ae) + catch (Exception ae) { String msg = messageService.getMessage(ERR_IS_WORKFLOW_DEPLOYED); throw new WorkflowException(msg, ae); @@ -910,8 +875,16 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { throw new IllegalAccessError("The process definition does not have an id!"); } - String key = idAttrib.getNodeValue(); - return factory.getDomainProcessKey(key); + + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + // Workflow-definition is deployed tenant-aware, key should be altered + return factory.getDomainProcessKey(idAttrib.getNodeValue()); + } + else + { + return idAttrib.getNodeValue(); + } } finally { @@ -968,6 +941,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } } + if(!activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + // Specify which tenant domain the workflow was started in using a variable + variables.put(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + // Start the process-instance ProcessInstance instance = runtimeService.startProcessInstanceById(processDefId, variables); if(instance.isEnded()) @@ -1137,7 +1116,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } }); } - + /** * Gets the Company Home * @@ -1145,30 +1124,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine */ private NodeRef getCompanyHome() { - if (tenantService.isEnabled()) - { - try - { - return tenantService.getRootNode(nodeService, unprotectedSearchService, namespaceService, - companyHomePath, nodeService.getRootNode(companyHomeStore)); - } - catch (RuntimeException re) - { - String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_INVALID, companyHomePath); - throw new IllegalStateException(msg, re); - } - } - else - { - List refs = unprotectedSearchService.selectNodes(nodeService.getRootNode(companyHomeStore), - companyHomePath, null, namespaceService, false); - if (refs.size() != 1) - { - String msg = messageService.getMessage(ERR_GET_COMPANY_HOME_MULTIPLE, companyHomePath, refs.size()); - throw new IllegalStateException(msg); - } - return refs.get(0); - } + return repositoryHelper.getCompanyHome(); } /** @@ -1219,35 +1175,11 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } /** - * Sets the Company Home Path - * - * @param companyHomePath + * @param repositoryHelper the repositoryHelper to set */ - public void setCompanyHomePath(String companyHomePath) + public void setRepositoryHelper(Repository repositoryHelper) { - this.companyHomePath = companyHomePath; - } - - /** - * Sets the Company Home Store - * - * @param companyHomeStore - */ - public void setCompanyHomeStore(String companyHomeStore) - { - this.companyHomeStore = new StoreRef(companyHomeStore); - } - - /** - * Set the unprotected search service - so we can find the node ref for - * company home when folk do not have read access to company home TODO: - * review use with DC - * - * @param unprotectedSearchService - */ - public void setUnprotectedSearchService(SearchService unprotectedSearchService) - { - this.unprotectedSearchService = unprotectedSearchService; + this.repositoryHelper = repositoryHelper; } /** @@ -1395,9 +1327,14 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { if(state == WorkflowTaskState.IN_PROGRESS) { - List tasks = taskService.createTaskQuery() - .taskAssignee(authority) - .list(); + TaskQuery taskQuery = taskService.createTaskQuery() + .taskAssignee(authority); + + if(!activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + taskQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + List tasks = taskQuery.list(); List resultingTasks = new ArrayList(); for(Task task : tasks) { @@ -1416,10 +1353,15 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } else { - List historicTasks = historyService.createHistoricTaskInstanceQuery() - .taskAssignee(authority) - .finished() - .list(); + HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery() + .taskAssignee(authority) + .finished(); + + if(activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + taskQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + List historicTasks =taskQuery.list(); List resultingTasks = new ArrayList(); for(HistoricTaskInstance historicTask : historicTasks) { @@ -1614,6 +1556,13 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { // Add task name TaskQuery taskQuery = taskService.createTaskQuery(); + + if (!activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + // Filter by tenant domain. + taskQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } + if (query.getTaskName() != null) { // Task 'key' is stored as variable on task @@ -1688,10 +1637,14 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine for(Entry customProperty : processCustomProps.entrySet()) { String name =factory.mapQNameToName(customProperty.getKey()); - - // Perform minimal property conversions - Object converted = propertyConverter.convertPropertyToValue(customProperty.getValue()); - taskQuery.processVariableValueEquals(name, converted); + + // Exclude the special "VAR_TENANT_DOMAIN" variable, this cannot be queried by users + if(name != ActivitiConstants.VAR_TENANT_DOMAIN) + { + // Perform minimal property conversions + Object converted = propertyConverter.convertPropertyToValue(customProperty.getValue()); + taskQuery.processVariableValueEquals(name, converted); + } } } @@ -1819,6 +1772,12 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine private List queryHistoricTasks(WorkflowTaskQuery query) { HistoricTaskInstanceQuery historicQuery = historyService.createHistoricTaskInstanceQuery().finished(); + + if (!activitiUtil.isMultiTenantWorkflowDeploymentEnabled()) + { + // Filter by tenant domain + historicQuery.processVariableValueEquals(ActivitiConstants.VAR_TENANT_DOMAIN, TenantUtil.getCurrentDomain()); + } if (query.getTaskId() != null) { @@ -1915,9 +1874,13 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine { String name =factory.mapQNameToName(customProperty.getKey()); - // Perform minimal property conversions - Object converted = propertyConverter.convertPropertyToValue(customProperty.getValue()); - taskQuery.processVariableValueEquals(name, converted); + // Exclude the special "VAR_TENANT_DOMAIN" variable, this cannot be queried by users + if(name != ActivitiConstants.VAR_TENANT_DOMAIN) + { + // Perform minimal property conversions + Object converted = propertyConverter.convertPropertyToValue(customProperty.getValue()); + taskQuery.processVariableValueEquals(name, converted); + } } } @@ -2189,7 +2152,6 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine private List getWorkflowsInternal(WorkflowInstanceQuery workflowInstanceQuery, boolean isActive) { String processDefId = workflowInstanceQuery.getWorkflowDefinitionId() == null ? null : createLocalId(workflowInstanceQuery.getWorkflowDefinitionId()); - LinkedList results = new LinkedList(); HistoricProcessInstanceQuery query; if (isActive) @@ -2312,10 +2274,8 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine } } - List completedInstances = query.list(); - List completedResults = typeConverter.convert(completedInstances); - results.addAll(completedResults); - return results; + List completedInstances = query.list(); + return typeConverter.convert(completedInstances); } /** diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java index 019fc5a648..1f4972a7b7 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiWorkflowManagerFactory.java @@ -21,6 +21,7 @@ package org.alfresco.repo.workflow.activiti; import org.activiti.engine.ProcessEngine; import org.alfresco.repo.i18n.MessageService; +import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authority.AuthorityDAO; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.workflow.BPMEngineRegistry; @@ -34,7 +35,6 @@ import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.namespace.NamespaceService; @@ -51,19 +51,18 @@ public class ActivitiWorkflowManagerFactory implements FactoryBean tasks = workflowService.queryTasks(taskQuery); assertNotNull(tasks); - // Activiti doesn't return start-task when no process/task id is set in query, so only 2 tasks will be returned assertEquals(3, tasks.size()); taskQuery = createWorkflowTaskQuery(WorkflowTaskState.COMPLETED); diff --git a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoBpmnParseListener.java b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoBpmnParseListener.java index 5a26faa1b9..08315a2439 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoBpmnParseListener.java +++ b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoBpmnParseListener.java @@ -50,6 +50,7 @@ public class AlfrescoBpmnParseListener implements BpmnParseListener private TaskListener createTaskListener; private ExecutionListener processCreateListener; private TenantService tenantService; + private boolean multiTenancyEnabled = true; @Override public void parseUserTask(Element userTaskElement, ScopeImpl scope, ActivityImpl activity) @@ -140,7 +141,7 @@ public class AlfrescoBpmnParseListener implements BpmnParseListener @Override public void parseCallActivity(Element callActivityElement, ScopeImpl scope, ActivityImpl activity) { - if (tenantService.isEnabled()) + if (multiTenancyEnabled && tenantService.isEnabled()) { ActivityBehavior activityBehavior = activity.getActivityBehavior(); if(activityBehavior instanceof CallActivityBehavior) @@ -196,11 +197,12 @@ public class AlfrescoBpmnParseListener implements BpmnParseListener for (ProcessDefinitionEntity processDefinition : arg1) { processDefinition.addExecutionListener(ExecutionListener.EVENTNAME_START, processCreateListener); - if (tenantService.isEnabled()) - { - String key = tenantService.getName(processDefinition.getKey()); - processDefinition.setKey(key); - } + + if (multiTenancyEnabled && tenantService.isEnabled()) + { + String key = tenantService.getName(processDefinition.getKey()); + processDefinition.setKey(key); + } } } @@ -234,6 +236,14 @@ public class AlfrescoBpmnParseListener implements BpmnParseListener { this.tenantService = tenantService; } + + /** + * @param deployInTenant whether or not workflows should be deployed as a tenant-only workflow + * when it's deployed IF the tenantService is enabled and a tenant-context is currently active. + */ + public void setDeployWorkflowsInTenant(boolean deployInTenant) { + this.multiTenancyEnabled = deployInTenant; + } @Override public void parseInclusiveGateway(Element inclusiveGwElement, diff --git a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java index 57bc8c9da1..be78473756 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java +++ b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java @@ -28,6 +28,7 @@ import org.activiti.engine.impl.jobexecutor.TimerExecuteNestedActivityJobHandler import org.activiti.engine.impl.variable.SerializableType; import org.activiti.engine.impl.variable.VariableType; import org.activiti.spring.SpringProcessEngineConfiguration; +import org.alfresco.service.cmr.repository.NodeService; /** * @author Nick Smith @@ -37,6 +38,7 @@ import org.activiti.spring.SpringProcessEngineConfiguration; public class AlfrescoProcessEngineConfiguration extends SpringProcessEngineConfiguration { private List customTypes; + private NodeService unprotectedNodeService; @Override protected void initVariableTypes() @@ -60,7 +62,7 @@ public class AlfrescoProcessEngineConfiguration extends SpringProcessEngineConfi // Wrap timer-job handler to handle authentication JobHandler timerJobHandler = jobHandlers.get(TimerExecuteNestedActivityJobHandler.TYPE); - JobHandler wrappingTimerJobHandler = new AuthenticatedTimerJobHandler(timerJobHandler); + JobHandler wrappingTimerJobHandler = new AuthenticatedTimerJobHandler(timerJobHandler, unprotectedNodeService); jobHandlers.put(TimerExecuteNestedActivityJobHandler.TYPE, wrappingTimerJobHandler); // Wrap async-job handler to handle authentication @@ -79,4 +81,9 @@ public class AlfrescoProcessEngineConfiguration extends SpringProcessEngineConfi { this.customTypes = customTypes; } + + public void setUnprotectedNodeService(NodeService unprotectedNodeService) + { + this.unprotectedNodeService = unprotectedNodeService; + } } diff --git a/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java b/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java index 319d2b4013..0633cef8ac 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java +++ b/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java @@ -26,8 +26,14 @@ import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.impl.persistence.entity.JobEntity; import org.activiti.engine.impl.pvm.PvmActivity; import org.activiti.engine.task.Task; +import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.repo.workflow.WorkflowConstants; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; /** * An {@link JobHandler} which executes activiti timer-jobs @@ -43,13 +49,25 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; public class AuthenticatedTimerJobHandler implements JobHandler { private JobHandler wrappedHandler; + private NodeService unprotectedNodeService; - public AuthenticatedTimerJobHandler(JobHandler jobHandler) + /** + * @param jobHandler the {@link JobHandler} to wrap. + * @param nodeService the UNPROTECTED {@link NodeService} to use for fetching initiator username + * when only tenant is known. We can't use initiator ScriptNode for this, because this uses the + * protected {@link NodeService} which requires an authenticated user in that tenant (see {@link #getInitiator(ActivitiScriptNode)}). + */ + public AuthenticatedTimerJobHandler(JobHandler jobHandler, NodeService nodeService) { if (jobHandler == null) { throw new IllegalArgumentException("JobHandler to delegate to is required"); } + if(nodeService == null) + { + throw new IllegalArgumentException("NodeService is required"); + } + this.unprotectedNodeService = nodeService; this.wrappedHandler = jobHandler; } @@ -58,41 +76,104 @@ public class AuthenticatedTimerJobHandler implements JobHandler final CommandContext commandContext) { String userName = null; - - PvmActivity targetActivity = execution.getActivity(); - if (targetActivity != null) + String tenantToRunIn = (String) execution.getVariable(ActivitiConstants.VAR_TENANT_DOMAIN); + if(tenantToRunIn != null && tenantToRunIn.trim().length() == 0) { - // Only try getting active task, if execution timer is waiting on is a userTask - String activityType = (String) targetActivity.getProperty(ActivitiConstants.NODE_TYPE); - if (ActivitiConstants.USER_TASK_NODE_TYPE.equals(activityType)) + tenantToRunIn = null; + } + + final ActivitiScriptNode initiatorNode = (ActivitiScriptNode) execution.getVariable(WorkflowConstants.PROP_INITIATOR); + + // Extracting the properties from the initiatornode should be done in correct tennant or as administrator, since we don't + // know who started the workflow yet (We can't access node-properties when no valid authentication context is set up). + if(tenantToRunIn != null) + { + userName = TenantUtil.runAsTenant(new TenantRunAsWork() { - Task task = new TaskQueryImpl(commandContext) + @Override + public String doWork() throws Exception + { + return getInitiator(initiatorNode); + } + }, tenantToRunIn); + } + else + { + // No tenant on worklfow, run as admin in default tenant + userName = AuthenticationUtil.runAs(new RunAsWork() + { + @SuppressWarnings("synthetic-access") + public String doWork() throws Exception + { + return getInitiator(initiatorNode); + } + }, AuthenticationUtil.getSystemUserName()); + } + + // Fall back to task assignee, if no initiator is found + if(userName == null) + { + PvmActivity targetActivity = execution.getActivity(); + if (targetActivity != null) + { + // Only try getting active task, if execution timer is waiting on is a userTask + String activityType = (String) targetActivity.getProperty(ActivitiConstants.NODE_TYPE); + if (ActivitiConstants.USER_TASK_NODE_TYPE.equals(activityType)) + { + Task task = new TaskQueryImpl(commandContext) .executionId(execution.getId()) .executeSingleResult(commandContext); - - if (task != null && task.getAssignee() != null) - { - userName = task.getAssignee(); + + if (task != null && task.getAssignee() != null) + { + userName = task.getAssignee(); + } } } } - // When no task assignee is set, use system user to run job + // When no task assignee is set, nor the initiator, use system user to run job if (userName == null) { userName = AuthenticationUtil.getSystemUserName(); + tenantToRunIn = null; } - // Execute timer - AuthenticationUtil.runAs(new RunAsWork() + if(tenantToRunIn != null) { - @SuppressWarnings("synthetic-access") - public Void doWork() throws Exception + TenantUtil.runAsUserTenant(new TenantRunAsWork() { - wrappedHandler.execute(job, configuration, execution, commandContext); - return null; - } - }, userName); + @Override + public Void doWork() throws Exception + { + wrappedHandler.execute(job, configuration, execution, commandContext); + return null; + } + }, userName, tenantToRunIn); + } + else + { + // Execute the timer without tenant + AuthenticationUtil.runAs(new RunAsWork() + { + @SuppressWarnings("synthetic-access") + public Void doWork() throws Exception + { + wrappedHandler.execute(job, configuration, execution, commandContext); + return null; + } + }, userName); + } + } + + protected String getInitiator(ActivitiScriptNode initiatorNode) + { + NodeRef ref = initiatorNode.getNodeRef(); + if(unprotectedNodeService.exists(ref)) + { + return (String) unprotectedNodeService.getProperty(ref, ContentModel.PROP_USERNAME); + } + return null; } @Override diff --git a/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java index d618d41b6f..47d0c39df5 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java +++ b/source/java/org/alfresco/repo/workflow/activiti/properties/ActivitiPropertyConverter.java @@ -156,6 +156,8 @@ public class ActivitiPropertyConverter public Map getPathProperties(String executionId) { Map variables = activitiUtil.getExecutionVariables(executionId); + variables.remove(ActivitiConstants.VAR_TENANT_DOMAIN); + Map properties = new HashMap(variables.size()); for (Entry entry: variables.entrySet()) { diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index e524ae18c8..f015ba5f30 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -324,7 +324,7 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine /* * @see org.alfresco.repo.workflow.WorkflowComponent#deployDefinition(java.io.InputStream, java.lang.String, java.lang.String) */ - public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype, final String name) + public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype, String name) { try { diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java index 33fb4741a0..6a65c8a5b6 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -633,4 +633,11 @@ public interface WorkflowService */ @Auditable(parameters = {"packageItem", "active"}) public List getPackageContents(String taskId); + + /** + * @return true, if all workflows (in workflowDeployers) have a copy deployed per tenant and workflows + * can be deployed in tenant. False when workflows are shared system-wide, regardless of the tenant context. + */ + @Auditable + public boolean isMultiTenantWorkflowDeploymentEnabled(); } diff --git a/source/java/org/alfresco/util/ApplicationContextHelper.java b/source/java/org/alfresco/util/ApplicationContextHelper.java index 426da08a55..74b59218ef 100644 --- a/source/java/org/alfresco/util/ApplicationContextHelper.java +++ b/source/java/org/alfresco/util/ApplicationContextHelper.java @@ -46,7 +46,6 @@ public class ApplicationContextHelper extends BaseApplicationContextHelper return BaseApplicationContextHelper.getApplicationContext(CONFIG_LOCATIONS); } - public static void main(String ... args) { ClassPathXmlApplicationContext ctx = (ClassPathXmlApplicationContext) getApplicationContext(); diff --git a/source/test-resources/activiti/test-activiti-component-context.xml b/source/test-resources/activiti/test-activiti-component-context.xml index b0c4163eee..12b585fbff 100644 --- a/source/test-resources/activiti/test-activiti-component-context.xml +++ b/source/test-resources/activiti/test-activiti-component-context.xml @@ -15,6 +15,11 @@ + + + + + @@ -23,9 +28,7 @@ - - - + @@ -60,6 +63,14 @@ + + + + + + + + diff --git a/source/test-resources/cluster-test/placeholder-test.xml b/source/test-resources/cluster-test/placeholder-test.xml new file mode 100644 index 0000000000..9bbcaab1a7 --- /dev/null +++ b/source/test-resources/cluster-test/placeholder-test.xml @@ -0,0 +1,166 @@ + + + + + ${alfresco.cluster.name} + ${alfresco.hazelcast.password} + + + 5701 + + + 224.2.2.3 + 54327 + + + 127.0.0.1 + + + my-access-key + my-secret-key + + us-west-1 + + hazelcast-sg + type + hz-nodes + + + + 10.10.1.* + + + + PBEWithMD5AndDES + + thesalt + + thepass + + 19 + + + + RSA/NONE/PKCS1PADDING + + thekeypass + + local + + JKS + + thestorepass + + keystore + + + + 16 + 64 + 60 + + + + 0 + + default + + + + 1 + + 0 + + 0 + + NONE + + 0 + + 25 + + hz.ADD_NEW_ENTRY + + + + + + \ No newline at end of file