mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
21981: Fixed ALF-2390: ContentService needs to return total/available space values where possible - Added ContentService getStoreTotalSpace and getStoreFreeSpace - Supported by underlying ContentStore methods - getSpaceUsed: Actual binary storage size (was getTotalSize, which is not deprecated) - getSpaceTotal: Total storage space on partition - getSpaceAvailable: Remaining storage space on partition - Added JMX methods - Note: getSpaceUsed is NOT exposed as a service method as it is too easy to abuse 21982: Follow-up to Fixed ALF-2390: Patch uses new 'getSizeUsed' instead of deprecated 'getTotalSize' 21983: Fixed ALF-766: Allow cache peer URLs to be set in ehcache-custom.xml - Was investigating settings required for client, so just added this in - By default, the 'cacheManagerPeerListenerFactory' uses no system properties, but a comment has been added on how to define hostName, port and socketTimeoutMillis. - Variables associated with that have been added to repository.properties and are exported to the Java system properties environment for EHCache to pick up. 21984: More ALF-766: Allow cache peer URLs to be set in ehcache-custom.xml - Added alfresco.ehcache.rmi.remoteObjectPort (see ALF-765) 22011: Fix for ALF-3005 - Blog and Discussion components not correctly checking length of modified HTML content 22074: Fix for ALF-4447 - Failure when publishing a Post - some reverse proxies changing the response header to lower case 22075: (RECORD ONLY) Merged HEAD to BRANCHES/DEV/V3.3-BUG-FIX: 22073: Resolve ALF-4522: Accessing document via /alfresco/service/api/path doesn't work - "Company Home" token is duplicated 22091: Merged PATCHES/V2.2.7 to V3.3-BUG-FIX (RECORD ONLY) 19832: Merged BRANCHES/V3.1 to PATCHES/V2.2.7: 17255: Fixed ETHREEOH-3180: Error appears when trying to search resources on Manage Task page 19778: Incremented version label 19769: ALF-2011: Backported dependencies ALF-2360: Merged V3.1 to PATCHES/V2.2.7 17314: ETHREEOH-3158: Fix RepoServerMgmt to work with external authentication methods - AuthenticationService.getCurrentTicket / getNewTicket now call pre authentication check before issuing a new ticket, thus still allowing ticket enforcement when external authentication is in use. ALF-2361: Merged V3.2 to PATCHES/V2.2.7 17456: Fix for: ETHREEOH-1465: It's impossible to get the login history for a given user (Audit) - all authentication routes (SSO and password) can now audit getting a new ticket for a session. SSO does not authenticate via the alfresco AuthenticationService API - you can now use auditing to track new sessions for users. 19558: Created hotfix branch off TAGS/ENTERPRISE/V2.2.7 19557: Tagged V2.2 build 533 Revision 19303 as TAGS/ENTERPRISE/V2.2.7 22092: Merged PATCHES/V3.1.0 to V3.3-BUG-FIX (RECORD ONLY) 21274: Merged PATCHES/V3.1.2 to PATCHES/V3.1.0 21264: ALF-3889: JBPMDeployProcessServlet not accessible by default - Should only be enabled in development environment 20242: ALF-2733: Recreate corrupt authorities produced by LDAP import 20240: ALF-2733: Recreate corrupt authorities produced by LDAP import 19032: Moved V3.1.0-ENTERPRISE-FINAL into ENTERPRISE/V3.1.0 13776: Tagged V3.1 rev 13700 (build 142) as V3.1.0-ENTERPRISE-FINAL 22093: Merged PATCHES/V3.1.1 to V3.3-BUG-FIX (RECORD ONLY) 21570: ALF-3687: Build classpath fix 21549: Merged PATCHES/V3.1.0 to PATCHES/V3.1.1 20221: Merged PATCHES/V3.1.2 to PATCHES/V3.1.0 20217: Merged PATCHES/V3.2.0 to PATCHES/V3.1.2 19793: Merged HEAD to V3.2.0 19786: Refactor of previous test fix. I have pushed down the OOo-specific parts of the change from AbstractContentTransformerTest to OpenOfficeContentTransformerTest leaving an extension point in the base class should other transformations need to be excluded in the future. 19785: Fix for failing test OpenOfficeContentTransformerTest.testAllConversions. Various OOo-related transformations are returned as available but fail on our test server with OOo on it. Pending further work on these failings, I am disabling those transformations in test code whilst leaving them available in the product code. This is because in the wild a different OOo version may succeed with these transformations. I had previously explicitly disabled 3 transformations in the product and I am moving that restriction from product to test code for the same reason. 19707: Return value from isTransformationBlocked was inverted. Fixed now. 19705: Refinement of previous check-in re OOo transformations. I have pulled up the code that handles blocked transformations into a superclass so that the JodConverter-based transformer worker can inherit the same list of blocked transformations. To reiterate, blocked transformations are those that the OOo integration code believes should work but which are broken in practice. These are blocked by the transformers and will always be unavailable regardless of the OOo connection state. 19702: Fix for HEAD builds running on panda build server. OOo was recently installed on panda which has activated various OOo-related transformations/extractions in the test code. It appears that OOo does not support some transformations from Office 97 to Office 2007. Specifically doc to docx and xls to xlsx. These transformations have now been marked as unavailable. 21548: Incremented version label 21547: ALF-4121: Merged V3.2 to PATCHES/V3.1.1 16827: ETHREEOH-2678 - Unfriendly system error occurs when trying to view Workflows information on content Details page if workflow was canceled 21275: Merged PATCHES/V3.1.2 to PATCHES/V3.1.1 21264: ALF-3889: JBPMDeployProcessServlet not accessible by default - Should only be enabled in development environment 21235: Removed old source zip 21233: Merged PATCHES/V3.1.2 to PATCHES/V3.1.1 20890: ALF-3687: Apply LUCENE-1383 patch to Lucene 2.1.0 to reduce memory leaks from ThreadLocals 20891: ALF-3687: Build classpath fix 21227: Merged PATCHES/V3.2.1 to PATCHES/V3.1.1 21207: Extra debug logging to track index triggering activity 21225: Merged V3.2 to PATCHES/V3.1.1 19598: Backported (merge not possible) HEAD rev 18790 for IndexInfo fixes - Hit problem where re-index threads were all waiting for merging, which had nothing to do 21223: Merged V3.2 to PATCHES/V3.1.1 16923: Lucene performance: avoid too many index deltas getting created by throttling transactions when the number of index entries gets above a configurable size - Stops performance degradation over time and out of memory errors under load 21222: Merged V3.2 to PATCHES/V3.1.1 16799: Fix for ETHREEOH-2843: lucene.indexer.minMergeDocs , lucene.indexer.mergeFactor and lucene.indexer.maxMergeDocs are never read / used - removed unused properties and parameterised the remaining unexposed properties 21220: Created hotfix branch off TAGS/ENTERPRISE/V3.1.1 19033: Moved V3.1.1-ENTERPRISE-FINAL into ENTERPRISE/V3.1.1 14897: Tagged V3.1 rev 14748 (build 229) as TAGS/V3.1.1-ENTERPRISE-FINAL 22096: Merged PATCHES/V3.1.2 to V3.3-BUG-FIX (RECORD ONLY) 22033: ALF-4504: Fix unit test classpath 22031: ALF-4503: Removed spurious mergeinfo 22030: ALF-4503: Merged V3.2 to PATCHES/V3.1.2 19518: ALF-757: Corrected audit config resource URL so that it resolves inside Tomcat as well as JUnit! 22028: Incremented version label 22026: ALF-4504: Merged HEAD to PATCHES/V3.1.2 19958: Updating pdfbox and fontbox libraries. The 3rd party libraries apache pdfbox and apache fontbox have been updated from version 0.8.0-incubating to version 1.1.0. Apache states that: [...] notable changes in this release include basic support for tagged PDF, various font handling improvements and better handling of CJK character sets. Source is available as before for pdfbox, but is no longer included for fontbox. 22025: ALF-4503: Merged V3.2 to PATCHES/V3.1.2 18000: Merged DEV/BELARUS/V3.2-2009_11_24 to V3.2 17719: ETHREEOH-3393: AuditConfig file is read every time the audit method is called in HibernateAuditDAO 19501: Merged DEV/BELARUS/V3.2-2010_02_24 to HEAD (with corrections) 19243: ALF-757: Cannot start up on JBoss 5.1 due to audit configuration error - Removed getPath() method because it is incompatible with JBoss and other app servers where resources can't be resolved to a file - Now use Spring ResourceLoader instead of creating FileInputStream - getLastModified() still returned where the resource resolves to a file; otherwise the server startup time 21699: Incremented version label 21697: ALF-4275: Merged PATCHES/V3.2.0 to PATCHES/V3.1.2 20349: Merged V3.3 to PATCHES/V3.2.0 20346: ALF-2839: Node pre-loading generates needless resultset rows - Added missing Criteria.list() call 20280: Fixed ALF-2839: Node pre-loading generates needless resultset rows - Split Criteria query to retrieve properties and aspects separately 21696: ALF-4275: Merged V3.3-BUG-FIX to PATCHES/V3.1.2 20231: Fixed ALF-2784: Degradation of performance between 3.1.1 and 3.2x (observed in JSF) - Handles warm caches more efficiently - Doesn't regress on ETWOTWO-949 - Can be backported safely 21694: ALF-4275: Merged PATCHES/V3.2.0 to PATCHES/V3.1.2 20266: Test reproduction of ALF-2839 failure: Node pre-loading generates needless resultset rows 17041: Merged V3.2 to V3.1 17023: Fixed parentAssocCache bug when adding assocs against an empty cache 16987: Merge 2.2 to 3.1: 13089: (record-only) Fix "Read-Write transaction" exception, when the user does not exist. ETWOTWO-1055. 13091: (record-only) Fix for NFS server "Read-Write transaction started within read-only transaction" exception. ETWOTWO-1054. 14190: (record-only) Fix for cut/paste a folder from Alfresco CIFS to local drive loses folder contents. ETWOTWO-1159. 14191: (record-only) Additional fix for CIFS 'No more connections' error. ETWOTWO-556 14199: (record-only) Fix for NFS problem with Solaris doing an Access check on the share level handle. ETWOTWO-1225. 14210: (record-only) Added support for FTP EPRT and EPSV commands, on IPv4 only. ETWOTWO-325. 14216: (record-only) Fixed FTP character encoding, ported UTF8 normalizer code from v3.x. ETWOTWO-1151. 14229: (record-only) Remove unused import. 14655: (record-only) Convert content I/O exceptions to file server exceptions during write and truncate. ETWOTWO-1241. 14825: (record-only) Add support for the extended response to the CIFS NTCreateAndX call, back port of ETWOTWO-1232. 15869: (record-only) Port of desktop action client side EXE fixes from v3.x. ETWOTWO-1374. 16727: Fix for unable to connect via FTP via Firefox (when anonymous logons are not enabled). ETHREEOH-2012. 16718: Fix for Alfresco and AVM spaces are empty when viewed by FTP and Alfresco is run as non-root. ETHREEOH-2652. Triggered when CIFS server uses default ports on Linux/Unix/Mac platforms and fails to start. 16717: Fixed setAllowConsoleShutdown setting in standalone server can cause infinite loop. JLAN-38. 16710: Added CIFS NT status code/text for the 'account locked' status, 0xC0000234. ETHREEOH-2897. 16709: Fixed the FTP not logged on status return code, now uses reply code 530. JLAN-90. 16666: Fix for CIFS cannot handle requests over 64K in JNI code, causes session disconnect, standalone server. JLAN-91. 16559: Fix for ACL parsing in the standalone JLAN Server build. JLAN-89. 16556: Fix for CIFS session leak and 100% CPU when connect/disconnecting quickly. ETHREEOH-2881. 16555: Fix for processing of NetBIOS packets over 64K in the older JNI code. Part of ETHREEOH-2882. 16309: Merged V2.2 to V3.1 (for rev 16305) 16304: Fix ETWOONE-335: Parallel Review does not store Priority and Due Date set at task creation 16305: ETWOONE-335: Parallel Review does not store Priority and Due Date set at task creation 16163: Added timstamp tracking via the file state cache, blend cached timestamps into file info/folder search results. Added support for . and .. pseudo entries in a wildcard folder search. 16162: Add support for the . and .. pseudo entries in a folder search. Return EA size as zero in CIFS file information levels. Added more debug output to notify change handler. 16160: Minor change to debug output 15827: Fixed bug in delete node event processing. 15780: Fix for MS Office document locking issue. ETHREEOH-2579. 15628: Update svn:mergeinfo 15627: Merge 3.2 to 3.1: 15626: Fixed NetBIOS reports an invalid packet during session connection, and connection stalls for a while. JLAN-86. 15572: Update svn:mergeinfo 15571: Merge 3.2 to 3.1: 15549: Check for null ClientInfo in the setCurrentUser() method and clear the auth context. Part of ETHREEOH-2538. 15550: Fixed performance issue in the continue search code, add warn level output of folder search timing. 15570: Merge 3.2 to 3.1: 15548: CIFS server memory leak fixes (clear auth context, session close). ETHREEOH-2538 15231: Fix for cut/paste file between folders on CIFS. ETHREEOH-2323. Added debug flags to the Alfresco filesystems, implemented in the ContentDiskDriver. Changed debug output to be single line. ENH-515. 14930: Updated svn:mergeinfo 14921: Merge HEAD to V3.1: 14599: Fixes to file server ACL parsing, part of ETHREEOH-2177 14916: Fixes for local domain lookup when WINS is configured. ETHREEOH-2263. 14523: Add trailing 'A' to CIFS server name, removed by recent checkin. 14484: Merged HEAD to v3.1: 13943 Added FTP IPv6 configuration, via the <IPv6 state="enabled|disabled"/> tag. Added the ftp.ipv6 property. MOB-714. 14483: Merged HEAD to v3.1: 13942 Added FTP IPv6 support. MOB-714. 22097: Merged PATCHES/V3.2.0 to V3.3-BUG-FIX (RECORD ONLY) 21556: Incremented version label 21555: ALF-4208: Merged PATCHES/V3.1.1 to PATCHES/V3.2.0 21225: Merged V3.2 to PATCHES/V3.1.1 19598: Backported (merge not possible) HEAD rev 18790 for IndexInfo fixes - Hit problem where re-index threads were all waiting for merging, which had nothing to do 17875: “Tagged V3.2 build 304 Revision 17823 as TAGS/V3.2.0-ENTERPRISE-FINAL” 22098: Merged PATCHES/V3.2.1 to V3.3-BUG-FIX (RECORD ONLY) 17876: Fix CMIS repo and folder browser web scripts. This can now be used as sample stand-alone JSR-168 portlet. 18309: ETHREEOH-4003: Impossibility to declare email as record 18378: ETHREEOH-4034: Permission exception when creating non-electronic records ... 18468: ETHREEOH-4105: Frozen Records Can Be Destroyed 18470: Part of ETHREEOH-4089: StoreModelObjectPersister fails to load model object - Added toString() methods to aid with diagnostics 18471: Fixed ETHREEOH-4089: StoreModelObjectPersister fails to load model object - If the XML fails to parse, then it is treated the same as a missing document. - Rather than having the system fail, the XML failures are reported (turn DEBUG on for full XML dump) and the document is treated the same as "store.hasDocument() = false" - Merges to HEAD will need merging to SpringFramework 18494: ETHREEOH-4089: StoreModelObjectPersister fails to load model object - Handle document parsing exceptions as well 19170: Fixed ALF-730: MLText is not fully carried during cut-paste or copy-paste - Fetching of properties for copy now uses 'mlAwareNodeService' 19286: Fix for https://issues.alfresco.com/jira/browse/ALF-626 "Using 'null' as an authority argument in clearPermissions() cause a java.lang.NullPointerException" 19406: Fix for ALF-649 - Web Service query() no longer returns metadata in 3.2 19597: Gave PropertyBackedBeanExporter a shorter name for it's cluster region name 19599: Fix ETHREEOH-2583: Make the index tracking "hole" retention period more configurable - Added property 'index.tracking.maxVoidRetentionTimeMinutes', which defaults to the 'maxTxnDurationMinutes' - Even more usefully, added 'index.tracking.minVoidCheckPeriodSeconds' that allows void checking to be less frequent defaulting to only doing it every 60s - Added explicit logging for voids: log4j.logger.org.alfresco.repo.node.index.IndexTransactionTracker.voids=DEBUG 19654: Fix blog test 19718: Merged HEAD to BRANCHES/V3.2: 19678: Fix PostgreSQL handling for null Serializable values (ALF-1614) - Provides generic way of targeting BLOB behaviour for different dialects - Fixes ALF-2301 by the way 19759: Fix for CIFS/CheckInOut.exe save of working copy breaks lock on original file. ALF-2028. 19760: Fix for working copy checked out via CIFS is not accessible until FileStateReaper expires file state. ALF-962. 20048: "Tagged V3.2 build 499 Revision 19935 as TAGS/ENTERPRISE.V3.2.1" 21166: Merged V3.3-BUG-FIX to PATCHES/V3.2.1 21165: Fixed ALF-3867: SQL format error when re-instating orphaned content URL - Parameter was not bounded with # - Added unit test to ensure SQL generated is correct 22101: Resolve ALF-4522: Accessing document via /alfresco/service/api/path doesn't work - "Company Home" token is duplicated - /cmis... urls accept paths relative to CMIS root path (which is /Company Home) - /api/path... urls accept paths relative to root of store 22103: Merge from HEAD to V3.3-BUG-FIX 22099: Fix for ALF-3733. Note that this should also fix ALF-4465. 22104: Merged V3.3 to V3.3-BUG-FIX 21690: ALF-3991: Disable audit behaviour when applying the RenditionModel.ASPECT_RENDITIONED aspect 21722: Merged PATCHES/V3.3.1 to V3.3 21721: ALF-4039: Extend store name encoding to all non-ASCII characters, as otherwise these are incorrectly mangled by the Tomcat 5 virtualization server when they appear in a web app path - Also use -x instead of _x so that the full encoded user name can be included in a DNS name - WCM preview now working with variety of user sandboxes with exotic characters in the user name! 21767: ALF-4234: CMIS Relationships (fix issue with source and target relationship type checking) 21789: ALF-4333: Fix - Updated RepoPrimaryManifestProcessorImpl so it can handle deletions that are reported by either pre-delete noderef or archived noderef (previously only handled the latter). - Updated TransferManifestNodeFactory so that it handles the case where the status of the node to transfer is "deleted". - Updated UnitTestTransferManifestNodeFactory so that it handles the change to TransferManifestNodeFactory above. - Added new tests for deletion cases. 21808: Fix for ALF-1908 - encoding for all arguments to all templates associated with WebScripts that can be exposed and executed via a URL. - this includes any freemarker templates in /components and /modules that can be executed via url and do not touch context objects that would cause them to be invalid when executed via that url, for example any component or template that touches page.* cannot be executed via a url in the browser, but those that do not *could* be exposed via a clever scripted URL and be manipulated to potentially contain XSS code. 21837: ALF-4039: In WebProjectServiceImpl.getWebUserRef() do not trust results of Lucene search alone. Certain special characters are ignored or treated as equivalent. Do string comparison to ensure exact match. 21843: Merged V3.3-BUG-FIX to V3.3 21137: ALF-3841: Alfresco Explorer SSO Authentication Filters now accept ticket parameters too - Can be turned back off with ntlm.authentication.browser.ticketLogons=false or kerberos.authentication.browser.ticketLogons=false - Wiki updated 21856: ALF-4391: Fix Share URL rewrite configuration - The URL rewrite configuration was not including the query string in the source URL meaning that it got lost in the rewritten URL (for some reason only on Websphere) - Solved this by using the urlrewrite.xml from ALF-260 and adding use-query-string="true" - Need to retest on Tomcat and WAS 21873: Fix ALF-2974: Locate file action is absent for documents in I'm editing tab in Repository 21907: ALF-4401: Web services not working on Websphere 7 (ever?) - Added in Sun SAAJ and JAXP reference implementations to shared library in order to make CXF work - Removed old Sun SAAJ libraries as these are embedded into JDK 1.6 21911: ALF-4399: Broken Repository Document library in Share on Websphere. (Fixing fallout from argument encoding in r21808) 21924: Further fixes for ALF-1908 - XSS argument encoding fixes in Forms runtime 21987: ALF-4187: Fix bitrock installer config so that the RMI communication ports are randomly selected and do not clash with the RMI registry port 21998: ALF-4323: Fix ability to use ECMA-357 ECMAScript for XML (E4X) expressions in Share on Weblogic (and Rules Management) - Use child first loading of org.apache.xmlbeans.* packages - Makes sense because it was originally donated by BEA 22105: Merged PATCHES/V3.2.r to V3.3-BUG-FIX (RECORD ONLY) 21082: Fixes for ALF-3777 and ALF-3778 - improvements to XSS attack mitigation. 21375: Fix ALF-3951 - XSS attack mitigation for IE6 browser. Also minor wiki code formatting fixes & HTML entities appearing in wiki dashlet. 21400: Merged BRANCHES/V3.2 to PATCHES/V3.2.r 19144: Added PRE tag to whitelist of safe tags for HTML stripping in Share. 19363: Fix for ALF-1952 - multi-pass HTML stripping 19814: Fix for ALF-2322 - discussion topic containing non-ascii characters cannot be saved 21759: Fix for IE6 XSS issue ALF-4307 22106: Merged PATCHES/V3.3.1 to V3.3-BUG-FIX (RECORD ONLY) 21838: Merged V3.3 to PATCHES/V3.3.1 21837: ALF-4039: In WebProjectServiceImpl.getWebUserRef() do not trust results of Lucene search alone. Certain special characters are ignored or treated as equivalent. Do string comparison to ensure exact match. 21941: ALF-4424: Merged V3.3-BUG-FIX to PATCHES/V3.3.1 21659: Workaround for ALF-4230: use of flash technology to upload documents into a share site makes the use of (some) external authentication methods difficult (or impossible) - The Flash uploader can be disabled via share-config: DocumentLibary / file-upload / adobe-flash-enabled 21944: Incremented version label 22079: ALF-4458: Merged PATCHES/V3.2.1 to PATCHES/V3.3.1 21606 ALF-4044: Introduced new policy.content.update.ignoreEmpty setting, that when true causes the repository to behave as it did before the fix to ALF-254. I.e. writing empty content will not trigger onContentPropertyUpdate policies or inbound content rules. This enables better compatibility with mac clients using CIFS or WebDAV; they actually create and close a file before appending its data. ALF-4458: Merged V3.3 to PATCHES/V3.3.1 20855: Fix for ALF-3690. Unable to FTP files into Share site documentLibrary folder. This fix corrects what it essentially a typo. The dictionaryService was injected twice into the CreateNodeRuleTrigger bean, rather than the dictionaryService and the ruleService. 22082: Incremented version label 22107: Merged PATCHES/V3.3.1 to V3.3-BUG-FIX 21943: ALF-4402: Use normalized repository user ID when doing filesystem quota tracking 22080: ALF-4458: Fixed broken logic in CreateNodeRuleTrigger.onAddAspect() - hasAspect() check was inverted because onAddAspect() is called before aspect is actually added git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@22108 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
933 lines
38 KiB
Java
933 lines
38 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.repo.content;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.io.Serializable;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
|
|
import javax.transaction.RollbackException;
|
|
import javax.transaction.UserTransaction;
|
|
|
|
import junit.framework.TestCase;
|
|
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.content.filestore.FileContentWriter;
|
|
import org.alfresco.repo.content.transform.ContentTransformer;
|
|
import org.alfresco.repo.policy.JavaBehaviour;
|
|
import org.alfresco.repo.policy.PolicyComponent;
|
|
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
import org.alfresco.service.ServiceRegistry;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.ContentData;
|
|
import org.alfresco.service.cmr.repository.ContentIOException;
|
|
import org.alfresco.service.cmr.repository.ContentReader;
|
|
import org.alfresco.service.cmr.repository.ContentService;
|
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
|
import org.alfresco.service.cmr.repository.NoTransformerException;
|
|
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.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.service.transaction.TransactionService;
|
|
import org.alfresco.util.ApplicationContextHelper;
|
|
import org.alfresco.util.GUID;
|
|
import org.alfresco.util.PropertyMap;
|
|
import org.alfresco.util.TempFileProvider;
|
|
import org.springframework.context.ApplicationContext;
|
|
|
|
/**
|
|
* @see org.alfresco.repo.content.RoutingContentService
|
|
*
|
|
* @author Derek Hulley
|
|
*/
|
|
public class RoutingContentServiceTest extends TestCase
|
|
{
|
|
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
|
|
|
private static final String SOME_CONTENT = "ABC";
|
|
|
|
private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/RoutingContentServiceTest";
|
|
|
|
private TransactionService transactionService;
|
|
private ContentService contentService;
|
|
private PolicyComponent policyComponent;
|
|
private NodeService nodeService;
|
|
private AuthenticationComponent authenticationComponent;
|
|
private UserTransaction txn;
|
|
private NodeRef rootNodeRef;
|
|
private NodeRef contentNodeRef;
|
|
|
|
public RoutingContentServiceTest()
|
|
{
|
|
}
|
|
|
|
@Override
|
|
public void setUp() throws Exception
|
|
{
|
|
transactionService = (TransactionService) ctx.getBean("TransactionService");
|
|
nodeService = (NodeService) ctx.getBean("NodeService");
|
|
contentService = (ContentService) ctx.getBean(ServiceRegistry.CONTENT_SERVICE.getLocalName());
|
|
this.policyComponent = (PolicyComponent) ctx.getBean("policyComponent");
|
|
this.authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent");
|
|
|
|
// authenticate
|
|
this.authenticationComponent.setSystemUserAsCurrentUser();
|
|
|
|
// start the transaction
|
|
txn = getUserTransaction();
|
|
txn.begin();
|
|
|
|
// create a store and get the root node
|
|
StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, getName());
|
|
if (!nodeService.exists(storeRef))
|
|
{
|
|
storeRef = nodeService.createStore(storeRef.getProtocol(), storeRef.getIdentifier());
|
|
}
|
|
rootNodeRef = nodeService.getRootNode(storeRef);
|
|
// create a content node
|
|
ContentData contentData = new ContentData(null, "text/plain", 0L, "UTF-16", Locale.CHINESE);
|
|
|
|
PropertyMap properties = new PropertyMap();
|
|
properties.put(ContentModel.PROP_CONTENT, contentData);
|
|
|
|
ChildAssociationRef assocRef = nodeService.createNode(
|
|
rootNodeRef,
|
|
ContentModel.ASSOC_CHILDREN,
|
|
QName.createQName(TEST_NAMESPACE, GUID.generate()),
|
|
ContentModel.TYPE_CONTENT,
|
|
properties);
|
|
contentNodeRef = assocRef.getChildRef();
|
|
}
|
|
|
|
@Override
|
|
public void tearDown() throws Exception
|
|
{
|
|
try
|
|
{
|
|
authenticationComponent.clearCurrentSecurityContext();
|
|
}
|
|
catch (Throwable e)
|
|
{
|
|
// ignore
|
|
}
|
|
try
|
|
{
|
|
if (txn != null)
|
|
{
|
|
txn.rollback();
|
|
}
|
|
}
|
|
catch (Throwable e)
|
|
{
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
private UserTransaction getUserTransaction()
|
|
{
|
|
return (UserTransaction) transactionService.getUserTransaction();
|
|
}
|
|
|
|
public void testSetUp() throws Exception
|
|
{
|
|
assertNotNull(contentService);
|
|
assertNotNull(nodeService);
|
|
assertNotNull(rootNodeRef);
|
|
assertNotNull(contentNodeRef);
|
|
assertNotNull(getUserTransaction());
|
|
assertFalse(getUserTransaction() == getUserTransaction()); // ensure txn instances aren't shared
|
|
}
|
|
|
|
/**
|
|
* Check that a valid writer into the content store can be retrieved and used.
|
|
*/
|
|
public void testSimpleNonTempWriter() throws Exception
|
|
{
|
|
ContentWriter writer = contentService.getWriter(null, null, false);
|
|
assertNotNull("Writer should not be null", writer);
|
|
assertNotNull("Content URL should not be null", writer.getContentUrl());
|
|
|
|
// write some content
|
|
writer.putContent(SOME_CONTENT);
|
|
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
|
|
writer.setEncoding("UTF-16");
|
|
writer.setLocale(Locale.CHINESE);
|
|
|
|
// set the content property manually
|
|
nodeService.setProperty(contentNodeRef, ContentModel.PROP_CONTENT, writer.getContentData());
|
|
|
|
// get the reader
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNotNull("Reader should not be null", reader);
|
|
assertNotNull("Content URL should not be null", reader.getContentUrl());
|
|
assertEquals("Content Encoding was not set", "UTF-16", reader.getEncoding());
|
|
assertEquals("Content Locale was not set", Locale.CHINESE, reader.getLocale());
|
|
}
|
|
|
|
/**
|
|
* Checks that the URL, mimetype and encoding are automatically set on the readers
|
|
* and writers
|
|
*/
|
|
public void testAutoSettingOfProperties() throws Exception
|
|
{
|
|
// get a writer onto the node
|
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
assertNotNull("Writer should not be null", writer);
|
|
assertNotNull("Content URL should not be null", writer.getContentUrl());
|
|
assertNotNull("Content mimetype should not be null", writer.getMimetype());
|
|
assertNotNull("Content encoding should not be null", writer.getEncoding());
|
|
assertNotNull("Content locale should not be null", writer.getLocale());
|
|
|
|
// write some content
|
|
writer.putContent(SOME_CONTENT);
|
|
|
|
// get the reader
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNotNull("Reader should not be null", reader);
|
|
assertNotNull("Content URL should not be null", reader.getContentUrl());
|
|
assertNotNull("Content mimetype should not be null", reader.getMimetype());
|
|
assertNotNull("Content encoding should not be null", reader.getEncoding());
|
|
assertNotNull("Content locale should not be null", reader.getLocale());
|
|
|
|
// check that the content length is correct
|
|
// - note encoding is important as we get the byte length
|
|
long length = SOME_CONTENT.getBytes(reader.getEncoding()).length; // ensures correct decoding
|
|
long checkLength = reader.getSize();
|
|
assertEquals("Content length incorrect", length, checkLength);
|
|
|
|
// check the content - the encoding will come into effect here
|
|
String contentCheck = reader.getContentString();
|
|
assertEquals("Content incorrect", SOME_CONTENT, contentCheck);
|
|
}
|
|
|
|
public void testWriteToNodeWithoutAnyContentProperties() throws Exception
|
|
{
|
|
// previously, the node was populated with the mimetype, etc
|
|
// check that the write has these
|
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, writer.getMimetype());
|
|
assertEquals("UTF-16", writer.getEncoding());
|
|
assertEquals(Locale.CHINESE, writer.getLocale());
|
|
|
|
// now remove the content property from the node
|
|
nodeService.setProperty(contentNodeRef, ContentModel.PROP_CONTENT, null);
|
|
|
|
writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
assertNull(writer.getMimetype());
|
|
assertEquals("UTF-8", writer.getEncoding());
|
|
assertEquals(Locale.getDefault(), writer.getLocale());
|
|
|
|
// now set it on the writer
|
|
writer.setMimetype("text/plain");
|
|
writer.setEncoding("UTF-16");
|
|
writer.setLocale(Locale.FRENCH);
|
|
|
|
String content = "The quick brown fox ...";
|
|
writer.putContent(content);
|
|
|
|
// the properties should have found their way onto the node
|
|
ContentData contentData = (ContentData) nodeService.getProperty(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertEquals("metadata didn't get onto node", writer.getContentData(), contentData);
|
|
|
|
// check that the reader's metadata is set
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertEquals("Metadata didn't get set on reader", writer.getContentData(), reader.getContentData());
|
|
}
|
|
|
|
public void testNullReaderForNullUrl() throws Exception
|
|
{
|
|
// set the property, but with a null URL
|
|
ContentData contentData = new ContentData(null, null, 0L, null);
|
|
nodeService.setProperty(contentNodeRef, ContentModel.PROP_CONTENT, contentData);
|
|
|
|
// get the reader
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNull("Reader must be null if the content URL is null", reader);
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
public void testContentStoreSizes() throws Exception
|
|
{
|
|
long contentTotalSize = contentService.getStoreFreeSpace();
|
|
long contentAvailableSize = contentService.getStoreTotalSpace();
|
|
}
|
|
|
|
public void testGetRawReader() throws Exception
|
|
{
|
|
ContentReader reader = contentService.getRawReader("test://non-existence");
|
|
assertNotNull("A reader is expected with content URL referencing no content", reader);
|
|
assertFalse("Reader should not have any content", reader.exists());
|
|
// Now write something
|
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, false);
|
|
writer.putContent("ABC from " + getName());
|
|
// Try again
|
|
String contentUrl = writer.getContentUrl();
|
|
reader = contentService.getRawReader(contentUrl);
|
|
assertNotNull("Expected reader for live, raw content", reader);
|
|
assertEquals("Content sizes don't match", writer.getSize(), reader.getSize());
|
|
}
|
|
|
|
/**
|
|
* Checks what happens when the physical content disappears
|
|
*/
|
|
public void testMissingContent() throws Exception
|
|
{
|
|
File tempFile = TempFileProvider.createTempFile(getName(), ".txt");
|
|
|
|
ContentWriter writer = new FileContentWriter(tempFile);
|
|
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
|
|
writer.setEncoding("UTF-8");
|
|
writer.putContent("What about the others? Buckwheats!");
|
|
// check
|
|
assertTrue("File does not exist", tempFile.exists());
|
|
assertTrue("File not written to", tempFile.length() > 0);
|
|
|
|
// update the node with this new info
|
|
ContentData contentData = writer.getContentData();
|
|
nodeService.setProperty(contentNodeRef, ContentModel.PROP_CONTENT, contentData);
|
|
|
|
// delete the content
|
|
tempFile.delete();
|
|
assertFalse("File not deleted", tempFile.exists());
|
|
|
|
// now attempt to get the reader for the node
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertFalse("Reader should indicate that content is missing", reader.exists());
|
|
|
|
// check the indexing doesn't spank everthing
|
|
txn.commit();
|
|
txn = null;
|
|
|
|
// cleanup
|
|
txn = getUserTransaction();
|
|
txn.begin();
|
|
nodeService.deleteNode(contentNodeRef);
|
|
txn.commit();
|
|
txn = null;
|
|
}
|
|
|
|
/**
|
|
* Tests simple writes that don't automatically update the node content URL
|
|
*/
|
|
public void testSimpleWrite() throws Exception
|
|
{
|
|
// get a writer to an arbitrary node
|
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, false); // no updating of URL
|
|
assertNotNull("Writer should not be null", writer);
|
|
|
|
// put some content
|
|
writer.putContent(SOME_CONTENT);
|
|
|
|
// get the reader for the node
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNull("No reader should yet be available for the node", reader);
|
|
}
|
|
|
|
private boolean policyFired = false;
|
|
private boolean readPolicyFired = false;
|
|
private boolean newContent = true;
|
|
|
|
/**
|
|
* Tests that the content update policy firs correctly
|
|
*/
|
|
public void testOnContentUpdatePolicy()
|
|
{
|
|
// Register interest in the content update event for a versionable node
|
|
this.policyComponent.bindClassBehaviour(
|
|
QName.createQName(NamespaceService.ALFRESCO_URI, "onContentUpdate"),
|
|
ContentModel.ASPECT_VERSIONABLE,
|
|
new JavaBehaviour(this, "onContentUpdateBehaviourTest"));
|
|
|
|
// First check that the policy is not fired when the versionable aspect is not present
|
|
ContentWriter contentWriter = this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
contentWriter.putContent("content update one");
|
|
assertFalse(this.policyFired);
|
|
|
|
this.newContent = false;
|
|
|
|
// Now check that the policy is fired when the versionable aspect is present
|
|
this.nodeService.addAspect(this.contentNodeRef, ContentModel.ASPECT_VERSIONABLE, null);
|
|
ContentWriter contentWriter2 = this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
contentWriter2.putContent("content update two");
|
|
assertTrue(this.policyFired);
|
|
this.policyFired = false;
|
|
|
|
// Check that the policy is not fired when using a non updating content writer
|
|
ContentWriter contentWriter3 = this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, false);
|
|
contentWriter3.putContent("content update three");
|
|
assertFalse(this.policyFired);
|
|
}
|
|
|
|
public void onContentUpdateBehaviourTest(NodeRef nodeRef, boolean newContent)
|
|
{
|
|
assertEquals(this.contentNodeRef, nodeRef);
|
|
assertEquals(this.newContent, newContent);
|
|
assertTrue(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE));
|
|
this.policyFired = true;
|
|
}
|
|
|
|
public void testOnContentReadPolicy()
|
|
{
|
|
// Register interest in the content read event for a versionable node
|
|
this.policyComponent.bindClassBehaviour(
|
|
QName.createQName(NamespaceService.ALFRESCO_URI, "onContentRead"),
|
|
ContentModel.ASPECT_VERSIONABLE,
|
|
new JavaBehaviour(this, "onContentReadBehaviourTest"));
|
|
|
|
// First check that the policy is not fired when the versionable aspect is not present
|
|
this.contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertFalse(this.readPolicyFired);
|
|
|
|
// Write some content and check that the policy is still not fired
|
|
ContentWriter contentWriter2 = this.contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
contentWriter2.putContent("content update two");
|
|
this.contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertFalse(this.readPolicyFired);
|
|
|
|
// Now check that the policy is fired when the versionable aspect is present
|
|
this.nodeService.addAspect(this.contentNodeRef, ContentModel.ASPECT_VERSIONABLE, null);
|
|
this.contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertTrue(this.readPolicyFired);
|
|
}
|
|
|
|
public void onContentReadBehaviourTest(NodeRef nodeRef)
|
|
{
|
|
this.readPolicyFired = true;
|
|
}
|
|
|
|
public void testTempWrite() throws Exception
|
|
{
|
|
// get a temporary writer
|
|
ContentWriter writer1 = contentService.getTempWriter();
|
|
// and another
|
|
ContentWriter writer2 = contentService.getTempWriter();
|
|
|
|
// check
|
|
assertNotSame("Temp URLs must be different",
|
|
writer1.getContentUrl(), writer2.getContentUrl());
|
|
}
|
|
|
|
/**
|
|
* Tests the automatic updating of nodes' content URLs
|
|
*/
|
|
public void testUpdatingWrite() throws Exception
|
|
{
|
|
// check that the content URL property has not been set
|
|
ContentData contentData = (ContentData) nodeService.getProperty(
|
|
contentNodeRef,
|
|
ContentModel.PROP_CONTENT);
|
|
assertNull("Content URL should be null", contentData.getContentUrl());
|
|
|
|
// before the content is written, there should not be any reader available
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNull("No reader should be available for new node", reader);
|
|
|
|
// get the writer
|
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
assertNotNull("No writer received", writer);
|
|
// write some content directly
|
|
writer.putContent(SOME_CONTENT);
|
|
|
|
// make sure that we can't reuse the writer
|
|
try
|
|
{
|
|
writer.putContent("DEF");
|
|
fail("Failed to prevent repeated use of the content writer");
|
|
}
|
|
catch (ContentIOException e)
|
|
{
|
|
// expected
|
|
}
|
|
|
|
// check that there is a reader available
|
|
reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNotNull("No reader available for node", reader);
|
|
String contentCheck = reader.getContentString();
|
|
assertEquals("Content fetched doesn't match that written", SOME_CONTENT, contentCheck);
|
|
|
|
// check that the content data was set
|
|
contentData = (ContentData) nodeService.getProperty(
|
|
contentNodeRef,
|
|
ContentModel.PROP_CONTENT);
|
|
assertNotNull("Content data not set", contentData);
|
|
assertEquals("Mismatched URL between writer and node",
|
|
writer.getContentUrl(), contentData.getContentUrl());
|
|
|
|
// check that the content size was set
|
|
assertEquals("Reader content length and node content length different",
|
|
reader.getSize(), contentData.getSize());
|
|
|
|
// check that the mimetype was set
|
|
assertEquals("Mimetype not set on content data", "text/plain", contentData.getMimetype());
|
|
// check encoding
|
|
assertEquals("Encoding not set", "UTF-16", contentData.getEncoding());
|
|
}
|
|
|
|
/**
|
|
* Checks that multiple writes can occur to the same node outside of any transactions.
|
|
* <p>
|
|
* It is only when the streams are closed that the node is updated.
|
|
*/
|
|
public void testConcurrentWritesNoTxn() throws Exception
|
|
{
|
|
// ensure that the transaction is ended - ofcourse, we need to force a commit
|
|
txn.commit();
|
|
txn = null;
|
|
|
|
ContentWriter writer1 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
ContentWriter writer2 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
ContentWriter writer3 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
|
|
writer1.putContent("writer1 wrote this");
|
|
writer2.putContent("writer2 wrote this");
|
|
writer3.putContent("writer3 wrote this");
|
|
|
|
// get the content
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
String contentCheck = reader.getContentString();
|
|
assertEquals("Content check failed", "writer3 wrote this", contentCheck);
|
|
}
|
|
|
|
public void testConcurrentWritesWithSingleTxn() throws Exception
|
|
{
|
|
// want to operate in a user transaction
|
|
txn.commit();
|
|
txn = null;
|
|
|
|
UserTransaction txn = getUserTransaction();
|
|
txn.begin();
|
|
txn.setRollbackOnly();
|
|
|
|
ContentWriter writer1 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
ContentWriter writer2 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
ContentWriter writer3 = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
|
|
writer1.putContent("writer1 wrote this");
|
|
writer2.putContent("writer2 wrote this");
|
|
writer3.putContent("writer3 wrote this");
|
|
|
|
// get the content
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
String contentCheck = reader.getContentString();
|
|
assertEquals("Content check failed", "writer3 wrote this", contentCheck);
|
|
|
|
try
|
|
{
|
|
txn.commit();
|
|
fail("Transaction has been marked for rollback");
|
|
}
|
|
catch (RollbackException e)
|
|
{
|
|
// expected
|
|
}
|
|
|
|
// rollback and check that the content has 'disappeared'
|
|
txn.rollback();
|
|
|
|
// need a new transaction
|
|
txn = getUserTransaction();
|
|
txn.begin();
|
|
txn.setRollbackOnly();
|
|
|
|
reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNull("Transaction was rolled back - no content should be visible", reader);
|
|
|
|
txn.rollback();
|
|
}
|
|
|
|
/**
|
|
* Create several threads that will attempt to write to the same node property.
|
|
* The ContentWriter is handed to the thread, so this checks that the stream closure
|
|
* uses the transaction that called <code>close</code> and not the transaction that
|
|
* fetched the <code>ContentWriter</code>.
|
|
*/
|
|
public synchronized void testConcurrentWritesWithMultipleTxns() throws Exception
|
|
{
|
|
// ensure that there is no content to read on the node
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNull("Reader should not be available", reader);
|
|
|
|
// commit node so that threads can see node
|
|
txn.commit();
|
|
txn = null;
|
|
|
|
String threadContent = "Thread content";
|
|
WriteThread[] writeThreads = new WriteThread[5];
|
|
for (int i = 0; i < writeThreads.length; i++)
|
|
{
|
|
ContentWriter threadWriter = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
writeThreads[i] = new WriteThread(threadWriter, threadContent);
|
|
// Kick each thread off
|
|
writeThreads[i].start();
|
|
}
|
|
|
|
// Wait for all threads to be waiting
|
|
outer:
|
|
while (true)
|
|
{
|
|
// Wait for each thread to be in a transaction
|
|
for (int i = 0; i < writeThreads.length; i++)
|
|
{
|
|
if (!writeThreads[i].isWaiting())
|
|
{
|
|
wait(10);
|
|
continue outer;
|
|
}
|
|
}
|
|
// All threads were waiting
|
|
break outer;
|
|
}
|
|
|
|
// Kick each thread into the stream close phase
|
|
for (int i = 0; i < writeThreads.length; i++)
|
|
{
|
|
synchronized(writeThreads[i])
|
|
{
|
|
writeThreads[i].notifyAll();
|
|
}
|
|
}
|
|
// Wait for the threads to complete (one way or another)
|
|
for (int i = 0; i < writeThreads.length; i++)
|
|
{
|
|
while (!writeThreads[i].isDone())
|
|
{
|
|
wait(10);
|
|
}
|
|
}
|
|
|
|
// check content has taken on thread's content
|
|
reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNotNull("Reader should now be available", reader);
|
|
String checkContent = reader.getContentString();
|
|
assertEquals("Content check failed", threadContent, checkContent);
|
|
}
|
|
|
|
public void testTransformation() throws Exception
|
|
{
|
|
// commit node so that threads can see node
|
|
txn.commit();
|
|
txn = null;
|
|
|
|
UserTransaction txn = getUserTransaction();
|
|
txn.begin();
|
|
txn.setRollbackOnly();
|
|
|
|
// get a regular writer
|
|
ContentWriter writer = contentService.getTempWriter();
|
|
writer.setMimetype("text/xml");
|
|
// write some stuff
|
|
String content = "<blah></blah>";
|
|
writer.putContent(content);
|
|
// get a reader onto the content
|
|
ContentReader reader = writer.getReader();
|
|
|
|
// get a new writer for the transformation
|
|
writer = contentService.getTempWriter();
|
|
writer.setMimetype("audio/x-wav"); // no such conversion possible
|
|
try
|
|
{
|
|
contentService.transform(reader, writer);
|
|
fail("Transformation attempted with invalid mimetype");
|
|
}
|
|
catch (NoTransformerException e)
|
|
{
|
|
// expected
|
|
}
|
|
|
|
// at this point, the transaction is unusable
|
|
txn.rollback();
|
|
|
|
txn = getUserTransaction();
|
|
txn.begin();
|
|
txn.setRollbackOnly();
|
|
|
|
writer.setMimetype("text/plain");
|
|
ContentTransformer transformer = contentService.getTransformer(reader.getMimetype(), writer.getMimetype());
|
|
assertNotNull("Expected a valid transformer", transformer);
|
|
contentService.transform(reader, writer);
|
|
// get the content from the writer
|
|
reader = writer.getReader();
|
|
assertEquals("Mimetype of target reader incorrect",
|
|
writer.getMimetype(), reader.getMimetype());
|
|
String contentCheck = reader.getContentString();
|
|
assertEquals("Content check failed", content, contentCheck);
|
|
|
|
txn.rollback();
|
|
}
|
|
|
|
/**
|
|
* Writes some content to the writer's output stream and then aquires
|
|
* a lock on the writer, waits until notified and then closes the
|
|
* output stream before terminating.
|
|
* <p>
|
|
* When firing thread up, be sure to call <code>notify</code> on the
|
|
* Thread instance in order to let the thread run to completion.
|
|
*/
|
|
private class WriteThread extends Thread
|
|
{
|
|
private ContentWriter writer;
|
|
private String content;
|
|
private volatile boolean isWaiting;
|
|
private volatile boolean isDone;
|
|
private volatile Throwable error;
|
|
|
|
public WriteThread(ContentWriter writer, String content)
|
|
{
|
|
this.writer = writer;
|
|
this.content = content;
|
|
isWaiting = false;
|
|
isDone = false;
|
|
error = null;
|
|
}
|
|
|
|
public boolean isWaiting()
|
|
{
|
|
return isWaiting;
|
|
}
|
|
|
|
public boolean isDone()
|
|
{
|
|
return isDone;
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
public Throwable getError()
|
|
{
|
|
return error;
|
|
}
|
|
|
|
public void run()
|
|
{
|
|
authenticationComponent.setSystemUserAsCurrentUser();
|
|
|
|
synchronized (this)
|
|
{
|
|
isWaiting = true;
|
|
try { this.wait(); } catch (InterruptedException e) {}; // wait until notified
|
|
}
|
|
|
|
final OutputStream os = writer.getContentOutputStream();
|
|
// Callback to write to the content in a new transaction
|
|
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>()
|
|
{
|
|
public Void execute() throws Throwable
|
|
{
|
|
try
|
|
{
|
|
// put the content
|
|
if (writer.getEncoding() == null)
|
|
{
|
|
os.write(content.getBytes());
|
|
}
|
|
else
|
|
{
|
|
os.write(content.getBytes(writer.getEncoding()));
|
|
}
|
|
os.close();
|
|
}
|
|
finally
|
|
{
|
|
if (os != null)
|
|
{
|
|
try { os.close(); } catch (IOException e) {}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
try
|
|
{
|
|
transactionService.getRetryingTransactionHelper().doInTransaction(callback);
|
|
}
|
|
catch (Throwable e)
|
|
{
|
|
e.printStackTrace();
|
|
error = e;
|
|
}
|
|
finally
|
|
{
|
|
isDone = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that the system is able to handle the uploading of content with an unknown mimetype.
|
|
* The unknown mimetype should be preserved, but treated just like an octet stream.
|
|
*/
|
|
public void testUnknownMimetype() throws Exception
|
|
{
|
|
String bogusMimetype = "text/bamboozle";
|
|
// get a writer onto the node
|
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
writer.setMimetype(bogusMimetype);
|
|
|
|
// write something in
|
|
writer.putContent(SOME_CONTENT);
|
|
|
|
// commit the transaction to ensure that it goes in OK
|
|
txn.commit();
|
|
|
|
// so far, so good
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNotNull("Should be able to get reader", reader);
|
|
assertEquals("Unknown mimetype was changed", bogusMimetype, reader.getMimetype());
|
|
}
|
|
|
|
/**
|
|
* Checks that node copy and delete behaviour behaves correctly w.r.t. cleanup and shared URLs
|
|
*/
|
|
public void testPostCopyContentRetrieval() throws Exception
|
|
{
|
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
|
|
writer.putContent("Some content");
|
|
ContentData writerContentData = writer.getContentData();
|
|
ContentData nodeContentData = (ContentData) nodeService.getProperty(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNotNull(nodeContentData);
|
|
assertEquals("ContentData not the same from NodeService and from ContentWriter", writerContentData, nodeContentData);
|
|
|
|
Map<QName, Serializable> copyProperties = nodeService.getProperties(contentNodeRef);
|
|
copyProperties.remove(ContentModel.PROP_NODE_UUID);
|
|
// Copy the node
|
|
NodeRef contentCopyNodeRef = nodeService.createNode(
|
|
rootNodeRef,
|
|
ContentModel.ASSOC_CHILDREN,
|
|
QName.createQName(TEST_NAMESPACE, GUID.generate()),
|
|
ContentModel.TYPE_CONTENT,
|
|
copyProperties).getChildRef();
|
|
// Now get and check the ContentData for the copy
|
|
ContentData copyNodeContentData = (ContentData) nodeService.getProperty(contentCopyNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNotNull(copyNodeContentData);
|
|
// The copy should share the same URL even
|
|
assertEquals("Copied node's cm:content ContentData was different", writerContentData, copyNodeContentData);
|
|
|
|
// Delete the first node and ensure that the second valud remains good and the content is editable
|
|
nodeService.deleteNode(contentNodeRef);
|
|
copyNodeContentData = (ContentData) nodeService.getProperty(contentCopyNodeRef, ContentModel.PROP_CONTENT);
|
|
assertNotNull(copyNodeContentData);
|
|
assertEquals("Post-delete value didn't remain the same", writerContentData, copyNodeContentData);
|
|
ContentReader copyNodeContentReader = contentService.getReader(contentCopyNodeRef, ContentModel.PROP_CONTENT);
|
|
assertTrue("Physical content was removed", copyNodeContentReader.exists());
|
|
|
|
txn.commit();
|
|
txn = null;
|
|
}
|
|
|
|
/**
|
|
* Ensure that content URLs outside of a transaction are not touched on rollback.
|
|
*/
|
|
public void testRollbackCleanup_ALF2890() throws Exception
|
|
{
|
|
ContentWriter updatingWriter = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
updatingWriter.putContent("STEP 1");
|
|
|
|
txn.commit();
|
|
txn = null;
|
|
|
|
ContentReader readerStep1 = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertEquals("Incorrect content", "STEP 1", readerStep1.getContentString());
|
|
|
|
ContentWriter simpleWriter = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, false);
|
|
simpleWriter.putContent("STEP 2");
|
|
readerStep1 = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertEquals("Incorrect content", "STEP 1", readerStep1.getContentString());
|
|
|
|
// Update the content
|
|
nodeService.setProperty(contentNodeRef, ContentModel.PROP_CONTENT, simpleWriter.getContentData());
|
|
ContentReader readerStep2 = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertEquals("Incorrect content", "STEP 2", readerStep2.getContentString());
|
|
|
|
simpleWriter = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, false);
|
|
simpleWriter.putContent("STEP 3");
|
|
ContentReader readerStep3 = simpleWriter.getReader();
|
|
assertEquals("Incorrect content", "STEP 3", readerStep3.getContentString());
|
|
readerStep2 = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertEquals("Incorrect content", "STEP 2", readerStep2.getContentString());
|
|
|
|
// Now get a ex-transaction writer but set the content property in a failing transaction
|
|
// Notice that we have already written "STEP 3" to an underlying binary
|
|
final ContentData simpleWriterData = simpleWriter.getContentData();
|
|
RetryingTransactionCallback<Void> failToSetPropCallback = new RetryingTransactionCallback<Void>()
|
|
{
|
|
public Void execute() throws Throwable
|
|
{
|
|
nodeService.setProperty(contentNodeRef, ContentModel.PROP_CONTENT, simpleWriterData);
|
|
throw new RuntimeException("aaa");
|
|
}
|
|
};
|
|
try
|
|
{
|
|
transactionService.getRetryingTransactionHelper().doInTransaction(failToSetPropCallback);
|
|
}
|
|
catch (RuntimeException e)
|
|
{
|
|
if (!e.getMessage().equals("aaa"))
|
|
{
|
|
throw e;
|
|
}
|
|
// Expected
|
|
}
|
|
// The writer data should not have been cleaned up
|
|
readerStep3 = simpleWriter.getReader();
|
|
assertTrue("Content was cleaned up when it originated outside of the transaction", readerStep3.exists());
|
|
assertEquals("Incorrect content", "STEP 3", readerStep3.getContentString());
|
|
// The node's content must be unchanged
|
|
readerStep2 = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertEquals("Incorrect content", "STEP 2", readerStep2.getContentString());
|
|
|
|
// Test that rollback cleanup works for writers fetched in the same transaction
|
|
final ContentReader[] readers = new ContentReader[1];
|
|
RetryingTransactionCallback<Void> rollbackCallback = new RetryingTransactionCallback<Void>()
|
|
{
|
|
public Void execute() throws Throwable
|
|
{
|
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true);
|
|
writer.putContent("UNLUCKY CONTENT");
|
|
ContentReader reader = contentService.getReader(contentNodeRef, ContentModel.PROP_CONTENT);
|
|
assertEquals("Incorrect content", "UNLUCKY CONTENT", reader.getContentString());
|
|
assertEquals("Incorrect content", "UNLUCKY CONTENT", writer.getReader().getContentString());
|
|
readers[0] = reader;
|
|
|
|
throw new RuntimeException("aaa");
|
|
}
|
|
};
|
|
try
|
|
{
|
|
transactionService.getRetryingTransactionHelper().doInTransaction(rollbackCallback);
|
|
}
|
|
catch (RuntimeException e)
|
|
{
|
|
if (!e.getMessage().equals("aaa"))
|
|
{
|
|
throw e;
|
|
}
|
|
// Expected
|
|
}
|
|
// Make sure that the content has been cleaned up
|
|
assertFalse("Content was not cleaned up after having been created in-transaction", readers[0].exists());
|
|
}
|
|
}
|