mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-07 18:25:23 +00:00
42174: ALF-14721: Merged PATCHES/V4.0.2 to V4.1-BUG-FIX 41782: ALF-15751: Merged DEV to V4.0.2 (4.0.2.14) 41704: ALF-15751: CLONE - Version History presents versions in wrong order 'VersionHistoryImpl' now sorts versions by node DB id because version with greater version number can't have id which is lesser than id of version with lesser version number. Additionally, this approach should be quicker than sorting by 'Modification date' and 'Version number' label. << Did not merge unit test, which was doing things with version branches that we don't normally support >> 42179: ALF-16149: Merged PATCHES/V4.0.1 to V4.1-BUG-FIX 41995: 41911: ALF-14127 User search retrieves all users from the DB regardless of search criteria - PeopleServiceImpl.getPeople(...) now calls a new method nonCannedGetPeopleQuery(...) rather than using the canned query which is slow with large numbers of users. 42011: 41911: ALF-14127 User search retrieves all users from the DB regardless of search criteria - Avoid NPE on params 42059: 41911: ALF-14127 User search retrieves all users from the DB regardless of search criteria - Ignore case broke one of the unit tests (now excluded from nonCanned version) 42188: French installer corrections from Gloria 42192: ALF-15906 - Share UI does not show the 'edit online' button for Visio documents 42195: Refactor of imapSpacesTemplates.acp into imapSpacesTemplates.xml and exploded content. This work is a necessary precursor to the fix for ALF-15803, which will add new localisations. 42220: Fix for ALF-16138. AbstractLinksWebScript doesn't cope with Links from deleted users. 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 42259: Fix to issue where multiple concurrent writes to same user preferences would cause exception to appear in Share when changing between old document library views and new views provided by a module. 42266: ALF-16154 - IE9: script error when click on workflow from document details page 42268: Fix for ALF-11152 - License Usage information always shows 0 users 42269: Fix for ALF-15211 - TinyMCE corrupting hyperlinks 42275: ALF-15993: alfresco log not removed if uninstalled on a different day - Fix from Bitrock - Also fixed for awe and share logs 42289: Merged DEV to V4.1-BUG-FIX 42276: ALF-1907: Check out rule is active for spaces - Unit test for checkout via action executer Fixed line endings and split asserts 42292: ALF-15937: updated the Javadoc of the checkin method to be in sync with what's in doc.alfresco.com 42307: Fix handling of syncmodeconfig=OFF when running 4.1.X locally without doing full enterprise build. 42308: Fix ALF-13968: Share DocLib sorting mixes files and folders - implicitly sort folders before files (~ pre 4.x) then selected sort option, such as name - also allow Alf-specific option with CMIS getChildren (eg. "orderBy=cmis:baseTypeId DESC,cmis:name ASC") 42310: Merged BRANCHES/DEV/BELARUS/V4.1-BUG-FIX-2012_09_24 to BRANCHES/DEV/V4.1-BUG-FIX: 42309: ALF-15707 (ALF-14691) - Any custom aspect or type (including ootb workflow) is not available for API calls like api/classes/<type or aspect> 42338: Merged BRANCHES/DEV/V3.4-BUG-FIX to BRANCHES/DEV/V4.1-BUG-FIX 42337: Fix for ALF-14764 - Moving a folder removes non-site Group permissions set, resets 'Inherit permissions flag' 42339: Fix for ALF-15151 - Selected group is illegible(black) in Admin console if High contrast theme is selected 42342: ALF-10362: Activities fail to log "name" changes with more than 1024 chars (eg. via Share "Create Content" form) - part I - fix Share config so that default "Create Content" form restricts to 255 chars as per other form config (eg. Edit Properties, inline rename, ...) 42353: Merged V3.4-BUG-FIX to V4.1-BUG-FIX (RECORD ONLY) 42281: Fix for ALF-9946 Need a supported solution for switching off content indexing (FTS) -> merge only to 4.1-BUG-FIX - remove references to isIndexed property which was removed in the back port 42360: ALF-16146: Fixed QName of the data list item type. 42361: ALF-10362: Activities fail to log "name" changes with more than 1024 chars (eg. via Share "Create Content" form) - part II - belts-and-braces (with unit test) 42362: Merged DEV to V4.1-BUG-FIX 42336: ALF-16160: office 2010 doesn't notify users of files being locked when using sharepoint protocol MS Office (if we enabled notification about document unlocking) periodically sends PROPFIND requests to get info about active locks. This code makes PROPFIND be able to send an info about locks for the MS Office 2010 client if a document was locked for edit offline. 42363: ALF-16213: renaming versioned file results in file being deleted. 42368: Record only merge V3.4-BUG-FIX (3.4.12) to V4.1-BUG-FIX (4.1.2) 42367: Merge V3.4 (3.4.11) to V3.4-BUG-FIX (3.4.12) 42366: ALF-16137: Merge V4.1 (4.1.1) to V3.4 (3.4.11) 42048: ALF-16005 Could not transform file size of 0 kb - Turns out that it was only doc ppt and xls zero byte files that had the problem. - Reverting part of revision 6473 (release 2.1 2007) AR-1251 (Version error when saving new content via CIFS) Dave W tells me that this is no longer an issue due to other changes 42381: Fixed ALF-16218: Solr GetNodes return status is 500 for Postgresql - Read-only webscript was calling through to "qnameDAO.getOrCreateQName", which could fail if the QName does not exist. Issue is not critical because it will start working once the QName gets created. 42384: ALF-15868 RepoTransferReceiverImplTest failing on MySQL Checked in a refactor of the transaction handling in the test to remove the suspicion that the current failure iis somehow a test error. (Still fails on MySQL) 42395: ALF-14353: Deploy pom files with dependencies to the Maven repo 42405: ALF-15986: Upgrade to Bitrock 8.5.0 in order to improve font scaling and adaptive layout with Gtk - Helps I18N 42407: Fixed 'state leak' from ActivityServiceImplTest 42408: Merged BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC to BRANCHES/DEV/V4.1-BUG-FIX: 42389: CLOUD-796: handle unknown custom content/folder type 42396: CLOUD-796: handle unknown custom content/folder type 42397: Merged BRANCHES/DEV/V4.1-BUG-FIX to BRANCHES/DEV/FEATURES/CLOUD1_CLOUDSYNC: 41858: ALF-14444 - CloudSync: Ensure unknown properties when synced to Cloud are ignored properly 42406: CLOUD-796: handle unknown custom content/folder type 42409: CloudSync: CLOUD-796 / ALF-16226 - hide sync props in forms (eg. edit props) for sync'ed custom content type 42419: Fixes: ALF-11096 - Ensures event edit button is disabled if the event came from Outlook (this is because VTI connector is one directional & changes can't be pushed back). 42420: Fix for ALF-16003 - Sync mode is incorrectly cached as off if repo hasn't started when the check is made. 42430: More refactoring of RepoTransferReceiverImplTest. 42441: Merged V4.1-BUG-FIX to HEAD 42440: ALF-16247: Thumbnails not rendering for PDFs with standard fonts - Because GS_LIB wasn't set on Linux and OSX 42452: Fix for ALF-15450 Share Lucene tool in admin console works incorrectly 42457: ALF-14347: Document workspace is incorrectly deleted - Check returned status code from delete method before continuing to delete components. 42458: ALF-15700: 'Imap Attachments' folder is not localized. - Added spaces.imap_attachments.childname property that allows the attachments folder to be localized 42459: ALF-16103: No easy way to specify a timeout for LDAP connections - Added ldap-authentication/ldap-ad-authentication property (ldap.authentication.java.naming.read.timeout) to configure the com.sun.jndi.ldap.read.timeout for the initialDirContextEnvironment. - ldap.authentication.java.naming.read.timeout property is configured in milliseconds. Defaults to zero (infinite) which is the current behavior. 42467: Fix for ALF-16275 SOLR include configuration to avoid indexing content - done and fixed all configuration to be treated as Java properties 42472: ALF-16175: Merged PATCHES/V4.0.1 to V4.1-BUG-FIX (Record Only) 42448: ALF-16096: Repo corruption in MT - clean-up assistance requested - Changed RepositoryAuthenticationDAO.getUserFolderLocation() to use getCurrentUserDomain() for its cache key. 42473: ALF-14838 ALF-14839 Deploy Maven artifacts containing the config and the test-resources, using these as classifiers 42475: ALF-14180 - CIFS - Cluster - doc and docx files are opened in read-only mode via MS Office 2003 and 2010 appropriately missed from check in 34544 42477: ALF-5051: Define ThumbnailDefinition Beans Outside of ThumbnailRegistry Bean - Reverted imgpreview to enterprise 4.1 size of 480 42504: Reverse Merge 42458 ALF-15700: 'Imap Attachments' folder is not localized. Causes unit test failures. 42517: ALF-15700: Restoring duff revision 42458 so that we can finish the job and fix it 42518: ALF-15700: Corrected internationalization of IMAP Attachments folder - RepositoryFolderConfigBean must look up paths by QName to be immune to localization and backward compatible - Must throw an error rather than using the store root if the path contains unresolved placeholders! - QName of attachments folder must remain "cm:Imap Attachments" because that's what it always was! 42528: ALF-16282: Hybrid Sync: folder unsync - sub-children still have sync indicators - fix typo fallout from ALF-15420 (r40782) + add unit/regress test 42529: ALF-16231: Corrected Imap Attachments English string 42530: ALF-14838 ALF-14839 Fix enterprise artifacts + deploy jars instead of zips 42531: ALF-14770 Cut / Paste triggers folder rules - Needed to disable rules on nodes being MOVED. - Added extra check to RuleTypeImpl when working out if a rule was disabled so that debug would not be misleading. No impact on logic, as RuleService does the same check later and discards the rules. 42546: ALF-15737 Audit trail does not show user login events - Also does not show any failed login events 42568: ALF-16077 CLONE: Incorrect activities if you try to add/edit/remove comment for document (if this document contains any title) The original activity feed comment code would include the title of a document, folder or blog rather than its name if it was available. - name is a mandatory field for a document and folder. - title is a mandatory field for a blog entry and its name may not be set via Share. Changing activity feed comment code so that the: - name is always used for documents and folders - title is always used for blogs 42571: ALF-14838 ALF-14839 Deploy config and test-resoruces artifacts in the same batch as the main artifact, otherwise they get different snapshot versions 42582: ALF-16255: CopiedFromAspectPatch fails on rules copied with a folder - Checked to make sure that cm:copiedfrom target is a cm:object before attempting a cm:original association. - Remove cm:copiedfrom aspect from source if cm:copiedfrom target is not a cm:object. 42593: ALF-16255: CopiedFromAspectPatch fails on rules copied with a folder - Corrections to log message and formatting. 42605: ALF-16231: Fixed broken IMAP unit tests 42612: Further fix for ALF-16164 Cloud monitoring of SOLR is CPU intensive due to its repeated use of the SOLR stats page - protect from dodgey JSON output 42624: ALF-14353: switch groupId to org.alfresco.enterprise, to be in sync with actual Maven deployment 42657: Fix for ALF-16359 Fix SOLR logging in production and other environments - configure in log4j-solr.properties anywhere on the solr web app classpath ... 42671: ALF-14353: fix facebook api dependency 42679: Merged V3.4-BUG-FIX to V4.1-BUG-FIX 42172: ALF-15262: Correct handling of linked rule deletion - When the last rule is removed from a folder and the ASPECT_RULES aspect is removed from its parent, we must cascade this removal to its secondary parents 42173: ALF-14400: Only site members can Edit Online (sharepoint) although the site is public and permissions allow editing for everybody - Rationalized the fix provided by Alex Malinovsky - Don't bother checking site memberships - let ACLs handle that and just check for permission to read the document 42182: Incremented version revision for 3.4.12 42243: ALF-15262: Further correction by Dmitry: use beforeRemoveAspect because beforeDeleteChildAssociation is not invoked on deletion of primary child associations 42278: ALF-12999: Correction by Alex M 42586: BDE-101: make .MD5 files suitable for easy check with md5sum -c 42627: Merged DEV to V3.4-BUG-FIX 42537: ALF-16139: Impossible to connect to CMIS via AtomPub and Web Services Activation libraries (including all Geronimo versions) have been removed because of a conflict with libraries in JBoss CXF WS installation. Also, 'javax.activation' is part of the JDK 1.6 (http://docs.oracle.com/javase/6/docs/api/javax/activation/DataHandler.html) 42677: Merged V3.4 to V3.4-BUG-FIX 42380: ALF-16220: Merged V4.1-BUG-FIX to V3.4 40590: ALF-15318: It was possible for a user with a disabled / expired account to log in via NTLM/SSO 40663: Merged DEV to V4.1-BUG-FIX 40661: ALF-15318 (part 2): It's possible to log in by disabled user (NTLM with SSO in a clustered env) The onValidateFailed() methods were moved to BaseSSOAuthenticationFilter to response with a 401 for a disabled user. 42556: ALF-15077: Site creation in Share is very very slow with over 15000 sites - Probably knock-on impact from us versioning secondary associations properly - Found old way of locating a leaf document to be ineffective as it would blow the caches (find all documents with the correct ID, then filter out the containers) - Effect was magnified when admin user was previously accessed via the explorer client and thus had an app:configurations child node, thus making admin a container and requiring its paths (e.g. zillions of nested group memberships) to be indexed - Instead, we have a new LEAFID field on leaves that we can use to efficiently locate a node to delete without hitting zillions of containers - Left backward compatible code to avoid requiring a full reindex 42557: ALF-16202: Merged V4.1-BUG-FIX to V3.4 40937: ALF-15702, ALF-15669: mmt-dependencies was messing up the SDK classpath 42566: ALF-15077: Correction to category-handling logic in container generation to fix failing unit tests 42608: Merged DEV to V3.4 42543: ALF-16248 : IE specific: It's impossible to create any event due to script error Correction for the fix for ALF-13623 to support IE8, also added clearing of 'allday' checkbox. 42622: ALF-16339: Group names incorrect in (non-site) "Manage Permissions" page - Site name was being used as the display name of all site groups! 42632: ALF-16354: Merged PATCHES/V3.4.6 to V3.4-BUG-FIX 42521: ALF-16231: Corrected LockUtils.isLockedOrReadOnly to properly handle the LOCK_EXPIRED status - Also fixed CheckOutCheckInService.checkout() to respect LOCK_EXPIRED but still disallow overwrite of unexpired WRITE_LOCKS 42522: ALF-16231: Further improvements - Renamed to isLockedAndReadOnly because that's what it means! 42644: ALF-16298: Cannot install RM amps on 4.1.1 - Passed command line arguments from shell script to mmt utility 42656: ALF-16298: Correction to DOS argument concatenation to allow multiple parameters separated by space 42664: ALF-16358: NPE detected during benchmark test. - Guarding against this in LeafScorer 42665: ALF-16360: Merged HEAD to V3.4 42440: ALF-16247: Thumbnails not rendering for PDFs with standard fonts - Because GS_LIB wasn't set on Linux and OSX 42447: ALF-16247: Thumbnails not rendering for PDFs with standard fonts - Fixes by Bitrock 42678: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 42244: Merged V3.4-BUG-FIX to V3.4 42172: ALF-15262: Correct handling of linked rule deletion - When the last rule is removed from a folder and the ASPECT_RULES aspect is removed from its parent, we must cascade this removal to its secondary parents 42243: ALF-15262: Further correction by Dmitry: use beforeRemoveAspect because beforeDeleteChildAssociation is not invoked on deletion of primary child associations 42279: Merged V3.4-BUG-FIX to V3.4 42278: ALF-12999: Correction by Alex M 42282: Merged V3.4-BUG-FIX to V3.4 42281: Fix for ALF-9946 Need a supported solution for switching off content indexing (FTS) -> merge only to 4.1-BUG-FIX - remove references to isIndexed property which was removed in the back port git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@42683 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1136 lines
46 KiB
Java
1136 lines
46 KiB
Java
/*
|
|
* Copyright (C) 2005-2012 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.repo.content;
|
|
|
|
import java.io.File;
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.alfresco.error.AlfrescoRuntimeException;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.avm.AVMNodeConverter;
|
|
import org.alfresco.repo.content.ContentServicePolicies.OnContentPropertyUpdatePolicy;
|
|
import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy;
|
|
import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy;
|
|
import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner;
|
|
import org.alfresco.repo.content.filestore.FileContentStore;
|
|
import org.alfresco.repo.content.filestore.FileContentWriter;
|
|
import org.alfresco.repo.content.transform.ContentTransformer;
|
|
import org.alfresco.repo.content.transform.ContentTransformerRegistry;
|
|
import org.alfresco.repo.content.transform.OOXMLThumbnailContentTransformer;
|
|
import org.alfresco.repo.content.transform.TransformerDebug;
|
|
import org.alfresco.repo.content.transform.UnimportantTransformException;
|
|
import org.alfresco.repo.node.NodeServicePolicies;
|
|
import org.alfresco.repo.policy.ClassPolicyDelegate;
|
|
import org.alfresco.repo.policy.JavaBehaviour;
|
|
import org.alfresco.repo.policy.PolicyComponent;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
import org.alfresco.service.cmr.avm.AVMService;
|
|
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
import org.alfresco.service.cmr.dictionary.InvalidTypeException;
|
|
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
|
|
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.MimetypeService;
|
|
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.cmr.repository.TransformationOptions;
|
|
import org.alfresco.service.cmr.usage.ContentQuotaException;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.util.EqualsHelper;
|
|
import org.alfresco.util.Pair;
|
|
import org.alfresco.util.TempFileProvider;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.springframework.beans.BeansException;
|
|
import org.springframework.context.ApplicationContext;
|
|
import org.springframework.context.ApplicationContextAware;
|
|
import org.springframework.extensions.surf.util.I18NUtil;
|
|
|
|
/**
|
|
* Service implementation acting as a level of indirection between the client
|
|
* and the underlying content store.
|
|
* <p>
|
|
* Note: This class was formerly the {@link RoutingContentService} but the
|
|
* 'routing' functionality has been pushed into the {@link AbstractRoutingContentStore store}
|
|
* implementations.
|
|
*
|
|
* @author Derek Hulley
|
|
* @since 3.2
|
|
*/
|
|
public class ContentServiceImpl implements ContentService, ApplicationContextAware
|
|
{
|
|
private static Log logger = LogFactory.getLog(ContentServiceImpl.class);
|
|
|
|
private DictionaryService dictionaryService;
|
|
private NodeService nodeService;
|
|
private AVMService avmService;
|
|
private MimetypeService mimetypeService;
|
|
private RetryingTransactionHelper transactionHelper;
|
|
private ApplicationContext applicationContext;
|
|
protected TransformerDebug transformerDebug;
|
|
|
|
|
|
/** a registry of all available content transformers */
|
|
private ContentTransformerRegistry transformerRegistry;
|
|
/** The cleaner that will ensure that rollbacks clean up after themselves */
|
|
private EagerContentStoreCleaner eagerContentStoreCleaner;
|
|
/** the store to use. Any multi-store support is provided by the store implementation. */
|
|
private ContentStore store;
|
|
/** the store for all temporarily created content */
|
|
private ContentStore tempStore;
|
|
private ContentTransformer imageMagickContentTransformer;
|
|
/** Should we consider zero byte content to be the same as no content? */
|
|
private boolean ignoreEmptyContent;
|
|
private boolean transformerFailover;
|
|
|
|
/**
|
|
* The policy component
|
|
*/
|
|
private PolicyComponent policyComponent;
|
|
|
|
/*
|
|
* Policies delegates
|
|
*/
|
|
ClassPolicyDelegate<ContentServicePolicies.OnContentUpdatePolicy> onContentUpdateDelegate;
|
|
ClassPolicyDelegate<ContentServicePolicies.OnContentPropertyUpdatePolicy> onContentPropertyUpdateDelegate;
|
|
ClassPolicyDelegate<ContentServicePolicies.OnContentReadPolicy> onContentReadDelegate;
|
|
|
|
public void setRetryingTransactionHelper(RetryingTransactionHelper helper)
|
|
{
|
|
this.transactionHelper = helper;
|
|
}
|
|
|
|
public void setDictionaryService(DictionaryService dictionaryService)
|
|
{
|
|
this.dictionaryService = dictionaryService;
|
|
}
|
|
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
public void setMimetypeService(MimetypeService mimetypeService)
|
|
{
|
|
this.mimetypeService = mimetypeService;
|
|
}
|
|
|
|
public void setTransformerRegistry(ContentTransformerRegistry transformerRegistry)
|
|
{
|
|
this.transformerRegistry = transformerRegistry;
|
|
}
|
|
|
|
public void setEagerContentStoreCleaner(EagerContentStoreCleaner eagerContentStoreCleaner)
|
|
{
|
|
this.eagerContentStoreCleaner = eagerContentStoreCleaner;
|
|
}
|
|
|
|
public void setStore(ContentStore store)
|
|
{
|
|
this.store = store;
|
|
}
|
|
|
|
public void setPolicyComponent(PolicyComponent policyComponent)
|
|
{
|
|
this.policyComponent = policyComponent;
|
|
}
|
|
|
|
public void setAvmService(AVMService service)
|
|
{
|
|
this.avmService = service;
|
|
}
|
|
|
|
public void setImageMagickContentTransformer(ContentTransformer imageMagickContentTransformer)
|
|
{
|
|
this.imageMagickContentTransformer = imageMagickContentTransformer;
|
|
}
|
|
|
|
public void setIgnoreEmptyContent(boolean ignoreEmptyContent)
|
|
{
|
|
this.ignoreEmptyContent = ignoreEmptyContent;
|
|
}
|
|
|
|
/**
|
|
* Allows fail over form one transformer to another when there is
|
|
* more than one transformer available. The cost is that the output
|
|
* of the transformer must go to a temporary file in case it fails.
|
|
* @param transformerFailover {@code true} indicate that fail over
|
|
* should take place.
|
|
*/
|
|
public void setTransformerFailover(boolean transformerFailover)
|
|
{
|
|
this.transformerFailover = transformerFailover;
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
|
|
*/
|
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
|
|
{
|
|
this.applicationContext = applicationContext;
|
|
}
|
|
|
|
/**
|
|
* Helper setter of the transformer debug.
|
|
* @param transformerDebug
|
|
*/
|
|
public void setTransformerDebug(TransformerDebug transformerDebug)
|
|
{
|
|
this.transformerDebug = transformerDebug;
|
|
}
|
|
|
|
/**
|
|
* Service initialise
|
|
*/
|
|
public void init()
|
|
{
|
|
// Set up a temporary store
|
|
this.tempStore = new FileContentStore(this.applicationContext, TempFileProvider.getTempDir().getAbsolutePath());
|
|
|
|
// Bind on update properties behaviour
|
|
this.policyComponent.bindClassBehaviour(
|
|
NodeServicePolicies.OnUpdatePropertiesPolicy.QNAME,
|
|
this,
|
|
new JavaBehaviour(this, "onUpdateProperties"));
|
|
|
|
// Register on content update policy
|
|
this.onContentUpdateDelegate = this.policyComponent.registerClassPolicy(OnContentUpdatePolicy.class);
|
|
this.onContentPropertyUpdateDelegate = this.policyComponent.registerClassPolicy(OnContentPropertyUpdatePolicy.class);
|
|
this.onContentReadDelegate = this.policyComponent.registerClassPolicy(OnContentReadPolicy.class);
|
|
}
|
|
|
|
/**
|
|
* Update properties policy behaviour
|
|
*
|
|
* @param nodeRef the node reference
|
|
* @param before the before values of the properties
|
|
* @param after the after values of the properties
|
|
*/
|
|
public void onUpdateProperties(
|
|
NodeRef nodeRef,
|
|
Map<QName, Serializable> before,
|
|
Map<QName, Serializable> after)
|
|
{
|
|
// ALF-254: empty files (0 bytes) do not trigger content rules
|
|
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Don't duplicate work when firing multiple policies
|
|
Set<QName> types = null;
|
|
OnContentPropertyUpdatePolicy propertyPolicy = null; // Doesn't change for the node instance
|
|
// Variables to control firing of node-level policies (any content change)
|
|
boolean fire = false;
|
|
boolean isNewContent = false;
|
|
// check if any of the content properties have changed
|
|
for (QName propertyQName : after.keySet())
|
|
{
|
|
// is this a content property?
|
|
PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName);
|
|
if (propertyDef == null)
|
|
{
|
|
// the property is not recognised
|
|
continue;
|
|
}
|
|
else if (!propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT))
|
|
{
|
|
// not a content type
|
|
continue;
|
|
}
|
|
else if (propertyDef.isMultiValued())
|
|
{
|
|
// We don't fire notifications for multi-valued content properties
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
ContentData beforeValue = (ContentData) before.get(propertyQName);
|
|
ContentData afterValue = (ContentData) after.get(propertyQName);
|
|
boolean hasContentBefore = ContentData.hasContent(beforeValue)
|
|
&& (!ignoreEmptyContent || beforeValue.getSize() > 0);
|
|
boolean hasContentAfter = ContentData.hasContent(afterValue)
|
|
&& (!ignoreEmptyContent || afterValue.getSize() > 0);
|
|
|
|
// There are some shortcuts here
|
|
if (!hasContentBefore && !hasContentAfter)
|
|
{
|
|
// Really, nothing happened
|
|
continue;
|
|
}
|
|
else if (EqualsHelper.nullSafeEquals(beforeValue, afterValue))
|
|
{
|
|
// Still, nothing happening
|
|
continue;
|
|
}
|
|
|
|
// Check for new content
|
|
isNewContent = isNewContent || !hasContentBefore && hasContentAfter;
|
|
|
|
// Make it clear when there's no content before or after
|
|
if (!hasContentBefore)
|
|
{
|
|
beforeValue = null;
|
|
}
|
|
if (!hasContentAfter)
|
|
{
|
|
afterValue = null;
|
|
}
|
|
|
|
// So debug ...
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
String name = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
|
|
logger.debug(
|
|
"Content property updated: \n" +
|
|
" Node Name: " + name + "\n" +
|
|
" Property: " + propertyQName + "\n" +
|
|
" Is new: " + isNewContent + "\n" +
|
|
" Before: " + beforeValue + "\n" +
|
|
" After: " + afterValue);
|
|
}
|
|
|
|
// Fire specific policy
|
|
types = getTypes(nodeRef, types);
|
|
if (propertyPolicy == null)
|
|
{
|
|
propertyPolicy = onContentPropertyUpdateDelegate.get(nodeRef, types);
|
|
}
|
|
propertyPolicy.onContentPropertyUpdate(nodeRef, propertyQName, beforeValue, afterValue);
|
|
|
|
// We also fire an event if *any* content property is changed
|
|
fire = true;
|
|
}
|
|
catch (ClassCastException e)
|
|
{
|
|
// properties don't conform to model
|
|
continue;
|
|
}
|
|
}
|
|
// fire?
|
|
if (fire)
|
|
{
|
|
// Fire the content update policy
|
|
types = getTypes(nodeRef, types);
|
|
OnContentUpdatePolicy policy = onContentUpdateDelegate.get(nodeRef, types);
|
|
policy.onContentUpdate(nodeRef, isNewContent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper method to lazily populate the types associated with a node
|
|
*
|
|
* @param nodeRef the node
|
|
* @param types any existing types
|
|
* @return the types - either newly populated or just what was passed in
|
|
*/
|
|
private Set<QName> getTypes(NodeRef nodeRef, Set<QName> types)
|
|
{
|
|
if (types != null)
|
|
{
|
|
return types;
|
|
}
|
|
types = new HashSet<QName>(this.nodeService.getAspects(nodeRef));
|
|
types.add(this.nodeService.getType(nodeRef));
|
|
return types;
|
|
}
|
|
|
|
@Override
|
|
public long getStoreFreeSpace()
|
|
{
|
|
return store.getSpaceFree();
|
|
}
|
|
|
|
@Override
|
|
public long getStoreTotalSpace()
|
|
{
|
|
return store.getSpaceTotal();
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public ContentReader getRawReader(String contentUrl)
|
|
{
|
|
ContentReader reader = null;
|
|
try
|
|
{
|
|
reader = store.getReader(contentUrl);
|
|
}
|
|
catch (UnsupportedContentUrlException e)
|
|
{
|
|
// The URL is not supported, so we spoof it
|
|
reader = new EmptyContentReader(contentUrl);
|
|
}
|
|
if (reader == null)
|
|
{
|
|
throw new AlfrescoRuntimeException("ContentStore implementations may not return null ContentReaders");
|
|
}
|
|
// set extra data on the reader
|
|
reader.setMimetype(MimetypeMap.MIMETYPE_BINARY);
|
|
reader.setEncoding("UTF-8");
|
|
reader.setLocale(I18NUtil.getLocale());
|
|
|
|
// Done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug(
|
|
"Direct request for reader: \n" +
|
|
" Content URL: " + contentUrl + "\n" +
|
|
" Reader: " + reader);
|
|
}
|
|
return reader;
|
|
}
|
|
|
|
public ContentReader getReader(NodeRef nodeRef, QName propertyQName)
|
|
{
|
|
return getReader(nodeRef, propertyQName, true);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private ContentReader getReader(NodeRef nodeRef, QName propertyQName, boolean fireContentReadPolicy)
|
|
{
|
|
ContentData contentData = null;
|
|
Serializable propValue = nodeService.getProperty(nodeRef, propertyQName);
|
|
if (propValue instanceof Collection)
|
|
{
|
|
Collection<Serializable> colPropValue = (Collection<Serializable>)propValue;
|
|
if (colPropValue.size() > 0)
|
|
{
|
|
propValue = colPropValue.iterator().next();
|
|
}
|
|
}
|
|
|
|
if (propValue instanceof ContentData)
|
|
{
|
|
contentData = (ContentData)propValue;
|
|
}
|
|
|
|
if (contentData == null)
|
|
{
|
|
PropertyDefinition contentPropDef = dictionaryService.getProperty(propertyQName);
|
|
|
|
// if no value or a value other content, and a property definition has been provided, ensure that it's CONTENT or ANY
|
|
if (contentPropDef != null &&
|
|
(!(contentPropDef.getDataType().getName().equals(DataTypeDefinition.CONTENT) ||
|
|
contentPropDef.getDataType().getName().equals(DataTypeDefinition.ANY))))
|
|
{
|
|
throw new InvalidTypeException("The node property must be of type content: \n" +
|
|
" node: " + nodeRef + "\n" +
|
|
" property name: " + propertyQName + "\n" +
|
|
" property type: " + ((contentPropDef == null) ? "unknown" : contentPropDef.getDataType()),
|
|
propertyQName);
|
|
}
|
|
}
|
|
|
|
// check that the URL is available
|
|
if (contentData == null || contentData.getContentUrl() == null)
|
|
{
|
|
// there is no URL - the interface specifies that this is not an error condition
|
|
return null;
|
|
}
|
|
String contentUrl = contentData.getContentUrl();
|
|
|
|
// The context of the read is entirely described by the URL
|
|
ContentReader reader = store.getReader(contentUrl);
|
|
if (reader == null)
|
|
{
|
|
throw new AlfrescoRuntimeException("ContentStore implementations may not return null ContentReaders");
|
|
}
|
|
|
|
// set extra data on the reader
|
|
reader.setMimetype(contentData.getMimetype());
|
|
reader.setEncoding(contentData.getEncoding());
|
|
reader.setLocale(contentData.getLocale());
|
|
|
|
// Fire the content read policy
|
|
if (reader != null && fireContentReadPolicy == true)
|
|
{
|
|
// Fire the content update policy
|
|
Set<QName> types = new HashSet<QName>(this.nodeService.getAspects(nodeRef));
|
|
types.add(this.nodeService.getType(nodeRef));
|
|
OnContentReadPolicy policy = this.onContentReadDelegate.get(nodeRef, types);
|
|
policy.onContentRead(nodeRef);
|
|
}
|
|
|
|
// we don't listen for anything
|
|
// result may be null - but interface contract says we may return null
|
|
return reader;
|
|
}
|
|
|
|
public ContentWriter getWriter(NodeRef nodeRef, QName propertyQName, boolean update)
|
|
{
|
|
if (nodeRef == null)
|
|
{
|
|
ContentContext ctx = new ContentContext(null, null);
|
|
// for this case, we just give back a valid URL into the content store
|
|
ContentWriter writer = store.getWriter(ctx);
|
|
// Register the new URL for rollback cleanup
|
|
eagerContentStoreCleaner.registerNewContentUrl(writer.getContentUrl());
|
|
// done
|
|
return writer;
|
|
}
|
|
|
|
// check for an existing URL - the get of the reader will perform type checking
|
|
ContentReader existingContentReader = getReader(nodeRef, propertyQName, false);
|
|
|
|
// get the content using the (potentially) existing content - the new content
|
|
// can be wherever the store decides.
|
|
ContentContext ctx = new NodeContentContext(existingContentReader, null, nodeRef, propertyQName);
|
|
ContentWriter writer = store.getWriter(ctx);
|
|
// Register the new URL for rollback cleanup
|
|
eagerContentStoreCleaner.registerNewContentUrl(writer.getContentUrl());
|
|
|
|
// Special case for AVM repository.
|
|
Serializable contentValue = null;
|
|
if (nodeRef.getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_AVM))
|
|
{
|
|
Pair<Integer, String> avmVersionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef);
|
|
contentValue = avmService.getContentDataForWrite(avmVersionPath.getSecond());
|
|
}
|
|
else
|
|
{
|
|
contentValue = nodeService.getProperty(nodeRef, propertyQName);
|
|
}
|
|
|
|
// set extra data on the reader if the property is pre-existing
|
|
if (contentValue != null && contentValue instanceof ContentData)
|
|
{
|
|
ContentData contentData = (ContentData)contentValue;
|
|
writer.setMimetype(contentData.getMimetype());
|
|
writer.setEncoding(contentData.getEncoding());
|
|
writer.setLocale(contentData.getLocale());
|
|
}
|
|
|
|
// attach a listener if required
|
|
if (update)
|
|
{
|
|
// need a listener to update the node when the stream closes
|
|
WriteStreamListener listener = new WriteStreamListener(nodeService, nodeRef, propertyQName, writer);
|
|
listener.setRetryingTransactionHelper(transactionHelper);
|
|
writer.addListener(listener);
|
|
|
|
}
|
|
|
|
// supply the writer with a copy of the mimetype service if needed
|
|
if (writer instanceof AbstractContentWriter)
|
|
{
|
|
((AbstractContentWriter)writer).setMimetypeService(mimetypeService);
|
|
}
|
|
|
|
// give back to the client
|
|
return writer;
|
|
}
|
|
|
|
/**
|
|
* @return Returns a writer to an anonymous location
|
|
*/
|
|
public ContentWriter getTempWriter()
|
|
{
|
|
// there is no existing content and we don't specify the location of the new content
|
|
return tempStore.getWriter(ContentContext.NULL_CONTEXT);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.repo.content.transform.ContentTransformerRegistry
|
|
* @see org.alfresco.repo.content.transform.ContentTransformer
|
|
* @see org.alfresco.service.cmr.repository.ContentService#transform(org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ContentWriter)
|
|
*/
|
|
public void transform(ContentReader reader, ContentWriter writer)
|
|
{
|
|
// Call transform with no options
|
|
TransformationOptions options = new TransformationOptions();
|
|
this.transform(reader, writer, options);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.repo.content.transform.ContentTransformerRegistry
|
|
* @see org.alfresco.repo.content.transform.ContentTransformer
|
|
* @deprecated
|
|
*/
|
|
public void transform(ContentReader reader, ContentWriter writer, Map<String, Object> options)
|
|
throws NoTransformerException, ContentIOException
|
|
{
|
|
transform(reader, writer, new TransformationOptions(options));
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.repo.content.transform.ContentTransformerRegistry
|
|
* @see org.alfresco.repo.content.transform.ContentTransformer
|
|
*/
|
|
public void transform(ContentReader reader, ContentWriter writer, TransformationOptions options)
|
|
throws NoTransformerException, ContentIOException
|
|
{
|
|
// check that source and target mimetypes are available
|
|
if (reader == null)
|
|
{
|
|
throw new AlfrescoRuntimeException("The content reader must be set");
|
|
}
|
|
String sourceMimetype = reader.getMimetype();
|
|
if (sourceMimetype == null)
|
|
{
|
|
throw new AlfrescoRuntimeException("The content reader mimetype must be set: " + reader);
|
|
}
|
|
String targetMimetype = writer.getMimetype();
|
|
if (targetMimetype == null)
|
|
{
|
|
throw new AlfrescoRuntimeException("The content writer mimetype must be set: " + writer);
|
|
}
|
|
|
|
long sourceSize = reader.getSize();
|
|
try
|
|
{
|
|
// look for a transformer
|
|
transformerDebug.pushAvailable(reader.getContentUrl(), sourceMimetype, targetMimetype, options);
|
|
List<ContentTransformer> transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options);
|
|
transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.transform(...)");
|
|
|
|
int count = transformers.size();
|
|
if (count == 0)
|
|
{
|
|
throw new NoTransformerException(sourceMimetype, targetMimetype);
|
|
}
|
|
|
|
if (count == 1 || !transformerFailover)
|
|
{
|
|
ContentTransformer transformer = transformers.size() == 0 ? null : transformers.get(0);
|
|
transformer.transform(reader, writer, options);
|
|
}
|
|
else
|
|
{
|
|
failoverTransformers(reader, writer, options, targetMimetype, transformers);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (transformerDebug.isEnabled())
|
|
{
|
|
transformerDebug.popAvailable();
|
|
debugActiveTransformers(sourceMimetype, targetMimetype, sourceSize, options);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void failoverTransformers(ContentReader reader, ContentWriter writer,
|
|
TransformationOptions options, String targetMimetype,
|
|
List<ContentTransformer> transformers)
|
|
{
|
|
List<AlfrescoRuntimeException> exceptions = null;
|
|
boolean done = false;
|
|
try
|
|
{
|
|
// Try the best transformer and then the next if it fails
|
|
// and so on down the list
|
|
char c = 'a';
|
|
String outputFileExt = mimetypeService.getExtension(targetMimetype);
|
|
for (ContentTransformer transformer : transformers)
|
|
{
|
|
ContentWriter currentWriter = writer;
|
|
File tempFile = null;
|
|
try
|
|
{
|
|
// We can't know in advance which of the
|
|
// available transformer will work - if any.
|
|
// We can't write into the ContentWriter stream.
|
|
// So make a temporary file writer with the
|
|
// current transformer name.
|
|
tempFile = TempFileProvider.createTempFile(
|
|
"FailoverTransformer_intermediate_"
|
|
+ transformer.getClass().getSimpleName() + "_", "."
|
|
+ outputFileExt);
|
|
currentWriter = new FileContentWriter(tempFile);
|
|
currentWriter.setMimetype(targetMimetype);
|
|
currentWriter.setEncoding(writer.getEncoding());
|
|
|
|
if (c != 'a' && transformerDebug.isEnabled())
|
|
{
|
|
transformerDebug.debug("");
|
|
transformerDebug.debug("Try " + c + ")");
|
|
}
|
|
c++;
|
|
|
|
transformer.transform(reader, currentWriter, options);
|
|
|
|
if (tempFile != null)
|
|
{
|
|
writer.putContent(tempFile);
|
|
}
|
|
|
|
// No need to close input or output streams
|
|
// (according
|
|
// to comment in FailoverContentTransformer)
|
|
done = true;
|
|
return;
|
|
}
|
|
catch (AlfrescoRuntimeException e)
|
|
{
|
|
if (exceptions == null)
|
|
{
|
|
exceptions = new ArrayList<AlfrescoRuntimeException>();
|
|
}
|
|
exceptions.add(e);
|
|
|
|
// Set a new reader to refresh the input stream.
|
|
reader = reader.getReader();
|
|
}
|
|
}
|
|
// Throw the exception from the first transformer. The
|
|
// others are consumed.
|
|
if (exceptions != null)
|
|
{
|
|
throw exceptions.get(0);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// Log exceptions that we have consumed. We may have thrown the first one if
|
|
// none of the transformers worked.
|
|
if (exceptions != null)
|
|
{
|
|
boolean first = true;
|
|
for (Exception e : exceptions)
|
|
{
|
|
Throwable rootCause = (e instanceof AlfrescoRuntimeException) ? ((AlfrescoRuntimeException)e).getRootCause() : null;
|
|
String message = (rootCause == null ? null : rootCause.getMessage());
|
|
if (done)
|
|
{
|
|
message = "Transformer succeeded after previous transformer failed"+ (message == null ? "" : ": "+message);
|
|
if (rootCause instanceof UnimportantTransformException)
|
|
{
|
|
logger.debug(message);
|
|
}
|
|
else
|
|
{
|
|
logger.error(message, e);
|
|
}
|
|
}
|
|
else if (!first) // The first exception is logged later
|
|
{
|
|
message = "Transformer exception"+ (message == null ? "" : ": "+message);
|
|
if (rootCause instanceof UnimportantTransformException)
|
|
{
|
|
logger.debug(message);
|
|
}
|
|
else
|
|
{
|
|
logger.error(message, e);
|
|
}
|
|
first = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.repo.content.transform.ContentTransformerRegistry
|
|
* @see org.alfresco.repo.content.transform.ContentTransformer
|
|
*/
|
|
public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype)
|
|
{
|
|
return getTransformer(null, sourceMimetype, -1, targetMimetype, new TransformationOptions());
|
|
}
|
|
|
|
public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype, TransformationOptions options)
|
|
{
|
|
return getTransformer(null, sourceMimetype, -1, targetMimetype, options);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.repository.ContentService#getTransformer(String, java.lang.String, long, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions)
|
|
*/
|
|
public ContentTransformer getTransformer(String sourceUrl, String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options)
|
|
{
|
|
List<ContentTransformer> transformers = getTransformers(sourceUrl, sourceMimetype, sourceSize, targetMimetype, options);
|
|
return (transformers == null) ? null : transformers.get(0);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.repository.ContentService#getTransformers(String, java.lang.String, long, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions)
|
|
*/
|
|
public List<ContentTransformer> getTransformers(String sourceUrl, String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options)
|
|
{
|
|
try
|
|
{
|
|
// look for a transformer
|
|
transformerDebug.pushAvailable(sourceUrl, sourceMimetype, targetMimetype, options);
|
|
List<ContentTransformer> transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options);
|
|
transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.getTransformer(...)");
|
|
return transformers.isEmpty() ? null : transformers;
|
|
}
|
|
finally
|
|
{
|
|
transformerDebug.popAvailable();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the file just uploaded into Share is a special "debugTransformers.txt" file and
|
|
* if it is creates TransformerDebug that lists all the supported mimetype transformation for
|
|
* each transformer.
|
|
*/
|
|
private void debugActiveTransformers(String sourceMimetype, String targetMimetype,
|
|
long sourceSize, TransformationOptions transformOptions)
|
|
{
|
|
// check the file name, but do faster tests first
|
|
if (sourceSize == 18 &&
|
|
MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(sourceMimetype) &&
|
|
MimetypeMap.MIMETYPE_IMAGE_PNG.equals(targetMimetype) &&
|
|
"debugTransformers.txt".equals(transformerDebug.getFileName(transformOptions, true, 0)))
|
|
{
|
|
Map<String, Set<String>> explicitTransforms = debugExplicitTransforms();
|
|
debugActiveTransformersByTransformer(explicitTransforms);
|
|
debugActiveTransformersByMimetypes(explicitTransforms);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates TransformerDebug that lists all the supported mimetype transformation for each transformer.
|
|
*/
|
|
private void debugActiveTransformersByTransformer(Map<String, Set<String>> explicitTransforms)
|
|
{
|
|
try
|
|
{
|
|
transformerDebug.pushMisc();
|
|
transformerDebug.debug("Active and inactive transformers");
|
|
TransformationOptions options = new TransformationOptions();
|
|
|
|
for (ContentTransformer transformer: transformerRegistry.getTransformers())
|
|
{
|
|
try
|
|
{
|
|
transformerDebug.pushMisc();
|
|
int mimetypePairCount = 0;
|
|
boolean first = true;
|
|
for (String sourceMimetype : mimetypeService.getMimetypes())
|
|
{
|
|
for (String targetMimetype : mimetypeService.getMimetypes())
|
|
{
|
|
if (transformer.isTransformable(sourceMimetype, -1, targetMimetype, options))
|
|
{
|
|
long maxSourceSizeKBytes = transformer.getMaxSourceSizeKBytes(
|
|
sourceMimetype, targetMimetype, options);
|
|
|
|
// Is this an explicit transform, ignored because there are explicit transforms
|
|
// or does not have explicit transforms.
|
|
Boolean explicit = transformer.isExplicitTransformation(sourceMimetype,
|
|
targetMimetype, options);
|
|
if (!explicit)
|
|
{
|
|
Set<String> targetMimetypes = explicitTransforms.get(sourceMimetype);
|
|
explicit = (targetMimetypes == null || !targetMimetypes.contains(targetMimetype))
|
|
? null
|
|
: Boolean.FALSE;
|
|
}
|
|
transformerDebug.activeTransformer(++mimetypePairCount, transformer,
|
|
sourceMimetype, targetMimetype, maxSourceSizeKBytes, explicit, first);
|
|
first = false;
|
|
}
|
|
}
|
|
}
|
|
if (first)
|
|
{
|
|
transformerDebug.inactiveTransformer(transformer);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
transformerDebug.popMisc();
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
transformerDebug.popMisc();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates TransformerDebug that lists all available transformers for each mimetype combination.
|
|
*/
|
|
private void debugActiveTransformersByMimetypes(Map<String, Set<String>> explicitTransforms)
|
|
{
|
|
try
|
|
{
|
|
transformerDebug.pushMisc();
|
|
transformerDebug.debug("Transformers for each mimetype combination");
|
|
TransformationOptions options = new TransformationOptions();
|
|
|
|
for (String sourceMimetype : mimetypeService.getMimetypes())
|
|
{
|
|
for (String targetMimetype : mimetypeService.getMimetypes())
|
|
{
|
|
try
|
|
{
|
|
transformerDebug.pushMisc();
|
|
int transformerCount = 0;
|
|
for (ContentTransformer transformer: transformerRegistry.getTransformers())
|
|
{
|
|
if (transformer.isTransformable(sourceMimetype, -1, targetMimetype, options))
|
|
{
|
|
long maxSourceSizeKBytes = transformer.getMaxSourceSizeKBytes(
|
|
sourceMimetype, targetMimetype, options);
|
|
|
|
// Is this an explicit transform, ignored because there are explicit transforms
|
|
// or does not have explicit transforms.
|
|
Boolean explicit = transformer.isExplicitTransformation(sourceMimetype,
|
|
targetMimetype, options);
|
|
if (!explicit)
|
|
{
|
|
Set<String> targetMimetypes = explicitTransforms.get(sourceMimetype);
|
|
explicit = (targetMimetypes == null || !targetMimetypes.contains(targetMimetype))
|
|
? null
|
|
: Boolean.FALSE;
|
|
}
|
|
transformerDebug.activeTransformer(sourceMimetype, targetMimetype,
|
|
transformerCount, transformer, maxSourceSizeKBytes, explicit,
|
|
transformerCount++ == 0);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
transformerDebug.popMisc();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
transformerDebug.popMisc();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the explicit mimetype transformations. Key is the source mimetype
|
|
* and the value is a set of target mimetypes that are explicit.
|
|
*/
|
|
private Map<String, Set<String>> debugExplicitTransforms()
|
|
{
|
|
Map<String, Set<String>> explicitTransforms = new HashMap<String, Set<String>>();
|
|
|
|
TransformationOptions options = new TransformationOptions();
|
|
for (String sourceMimetype : mimetypeService.getMimetypes())
|
|
{
|
|
for (String targetMimetype : mimetypeService.getMimetypes())
|
|
{
|
|
for (ContentTransformer transformer : transformerRegistry.getTransformers())
|
|
{
|
|
if (transformer.isTransformable(sourceMimetype, -1, targetMimetype, options))
|
|
{
|
|
if (transformer.isExplicitTransformation(sourceMimetype, targetMimetype,
|
|
options))
|
|
{
|
|
Set<String> targetMimetypes = explicitTransforms.get(sourceMimetype);
|
|
if (targetMimetypes == null)
|
|
{
|
|
targetMimetypes = new HashSet<String>();
|
|
explicitTransforms.put(sourceMimetype, targetMimetypes);
|
|
}
|
|
targetMimetypes.add(targetMimetype);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return explicitTransforms;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public long getMaxSourceSizeBytes(String sourceMimetype, String targetMimetype, TransformationOptions options)
|
|
{
|
|
try
|
|
{
|
|
long maxSourceSize = 0;
|
|
transformerDebug.pushAvailable(null, sourceMimetype, targetMimetype, options);
|
|
List<ContentTransformer> transformers = getActiveTransformers(sourceMimetype, -1, targetMimetype, options);
|
|
for (ContentTransformer transformer: transformers)
|
|
{
|
|
long maxSourceSizeKBytes = transformer.getMaxSourceSizeKBytes(sourceMimetype, targetMimetype, options);
|
|
if (maxSourceSize >= 0)
|
|
{
|
|
if (maxSourceSizeKBytes < 0)
|
|
{
|
|
maxSourceSize = -1;
|
|
}
|
|
else if (maxSourceSizeKBytes > 0 && maxSourceSize < maxSourceSizeKBytes)
|
|
{
|
|
maxSourceSize = maxSourceSizeKBytes;
|
|
}
|
|
}
|
|
// if maxSourceSizeKBytes == 0 this implies the transformation is disabled
|
|
}
|
|
if (transformerDebug.isEnabled())
|
|
{
|
|
transformerDebug.availableTransformers(transformers, -1,
|
|
"ContentService.getMaxSourceSizeBytes() = "+transformerDebug.fileSize(maxSourceSize*1024));
|
|
}
|
|
return (maxSourceSize > 0) ? maxSourceSize * 1024 : maxSourceSize;
|
|
}
|
|
finally
|
|
{
|
|
transformerDebug.popAvailable();
|
|
}
|
|
}
|
|
|
|
public List<ContentTransformer> getActiveTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options)
|
|
{
|
|
return getActiveTransformers(sourceMimetype, -1, targetMimetype, options);
|
|
}
|
|
|
|
public List<ContentTransformer> getActiveTransformers(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options)
|
|
{
|
|
return transformerRegistry.getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.repository.ContentService#getImageTransformer()
|
|
*/
|
|
public ContentTransformer getImageTransformer()
|
|
{
|
|
return imageMagickContentTransformer;
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.repo.content.transform.ContentTransformerRegistry
|
|
* @see org.alfresco.repo.content.transform.ContentTransformer
|
|
*/
|
|
public boolean isTransformable(ContentReader reader, ContentWriter writer)
|
|
{
|
|
return isTransformable(reader, writer, new TransformationOptions());
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.repository.ContentService#isTransformable(org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ContentWriter, org.alfresco.service.cmr.repository.TransformationOptions)
|
|
*/
|
|
public boolean isTransformable(ContentReader reader, ContentWriter writer, TransformationOptions options)
|
|
{
|
|
// check that source and target mimetypes are available
|
|
String sourceMimetype = reader.getMimetype();
|
|
if (sourceMimetype == null)
|
|
{
|
|
throw new AlfrescoRuntimeException("The content reader mimetype must be set: " + reader);
|
|
}
|
|
String targetMimetype = writer.getMimetype();
|
|
if (targetMimetype == null)
|
|
{
|
|
throw new AlfrescoRuntimeException("The content writer mimetype must be set: " + writer);
|
|
}
|
|
|
|
long sourceSize = reader.getSize();
|
|
try
|
|
{
|
|
// look for a transformer
|
|
transformerDebug.pushAvailable(reader.getContentUrl(), sourceMimetype, targetMimetype, options);
|
|
List<ContentTransformer> transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options);
|
|
transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.isTransformable(...)");
|
|
|
|
return transformers.size() > 0;
|
|
}
|
|
finally
|
|
{
|
|
transformerDebug.popAvailable();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures that, upon closure of the output stream, the node is updated with
|
|
* the latest URL of the content to which it refers.
|
|
* <p>
|
|
*
|
|
* @author Derek Hulley
|
|
*/
|
|
private static class WriteStreamListener extends AbstractContentStreamListener
|
|
{
|
|
private NodeService nodeService;
|
|
private NodeRef nodeRef;
|
|
private QName propertyQName;
|
|
private ContentWriter writer;
|
|
|
|
public WriteStreamListener(
|
|
NodeService nodeService,
|
|
NodeRef nodeRef,
|
|
QName propertyQName,
|
|
ContentWriter writer)
|
|
{
|
|
this.nodeService = nodeService;
|
|
this.nodeRef = nodeRef;
|
|
this.propertyQName = propertyQName;
|
|
this.writer = writer;
|
|
}
|
|
|
|
public void contentStreamClosedImpl() throws ContentIOException
|
|
{
|
|
try
|
|
{
|
|
// set the full content property
|
|
ContentData contentData = writer.getContentData();
|
|
// Bypass NodeService for avm stores.
|
|
if (nodeRef.getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_AVM))
|
|
{
|
|
nodeService.setProperty(nodeRef, ContentModel.PROP_CONTENT, contentData);
|
|
}
|
|
else
|
|
{
|
|
nodeService.setProperty(
|
|
nodeRef,
|
|
propertyQName,
|
|
contentData);
|
|
}
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Stream listener updated node: \n" +
|
|
" node: " + nodeRef + "\n" +
|
|
" property: " + propertyQName + "\n" +
|
|
" value: " + contentData);
|
|
}
|
|
}
|
|
catch (ContentQuotaException qe)
|
|
{
|
|
throw qe;
|
|
}
|
|
catch (Throwable e)
|
|
{
|
|
throw new ContentIOException("Failed to set content property on stream closure: \n" +
|
|
" node: " + nodeRef + "\n" +
|
|
" property: " + propertyQName + "\n" +
|
|
" writer: " + writer + "\n" +
|
|
e.toString(),
|
|
e);
|
|
}
|
|
}
|
|
}
|
|
} |