Alan Davis 819988c518 ALF-12273: Merge V4.0-BUG-FIX to HEAD
33119: Merge V3.4-BUG-FIX (3.4.8) to V4.0-BUG-FIX (4.0.1)
      33099: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
      ALF-10976 Excel files bigger than 2mb cause soffice.exe to take 100% of one CPU for more than 2 minutes in previews.
         - Polish TransformerDebug
         - Better config for txt and xlsx to swf   
      33095: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
      ALF-10976 Excel files bigger than 2mb cause soffice.exe to take 100% of one CPU for more than 2 minutes in previews.
         - Improvements to TransformerDebug so that calls to getTransformers use trace rather than debug level logging
           allowing one to see the wood for the trees  
      33016: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
      ALF-10976 Excel files bigger than 2mb cause soffice.exe to take 100% of one CPU for more than 2 minutes in previews.
         - fix build errors - may not get all of them as not tested on Linux
      33005: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
      ALF-10976 Excel files bigger than 2mb cause soffice.exe to take 100% of one CPU for more than 2 minutes in previews.
         - Disable transformers if the source txt or xlsx is too large - avoids transforms that don't finish
           txt limit is 5MB
      	 xlsx limit is 1MB
         - Added a general 2 minute timeout added (ignored by JOD transformers - which already have a 2 minute timeout 
      	 and OpenOffice transformers - would require more work)
         - Previous commit already limited txt -> pdf -> png so that only the 1st pdf page was created when creating a doclib icon
         - Earlier commit already reduced the priority of the background Thread used for transformations so that user interaction
           did not suffer.
      33004: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
      ALF-10976 Excel files bigger than 2mb cause soffice.exe to take 100% of one CPU for more than 2 minutes in previews.
         - Added time, size and page limits to transformer configuration to allow one to avoid
           costly transformations and to stop them if they do occur. Limits avoid a transformer being
           selected if the source is too large, or make it throw and Exception or discard data after a given
           time, KBytes read or pages created.
         - Page limits currently only used by TextToPdfContentTransformer for thumbnail (icon) creation.
         - Time, Size and Page limits are currently ignored by JodContentTransformer and OpenOfficeContentTransformerWorker
           once running but the max source size limits may be used to avoid the selection of the transformer in the first
           place.
         - TransformerDebug added to be able to see what is going on. A real eye opener!
           log4j org.alfresco.repo.content.transform.TransformerDebug
      32136: ALF-10412 Nonreducing 100% CPU Uploading Large Files to Share Site Document Library
         Reducing the priority of the async thread pool that is used to perform the transformations so that normal activity (and even garbage collection) is not interrupted by transformations. 


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@33223 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2012-01-13 17:25:32 +00:00

748 lines
30 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.Serializable;
import java.util.Collection;
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.transform.ContentTransformer;
import org.alfresco.repo.content.transform.ContentTransformerRegistry;
import org.alfresco.repo.content.transform.TransformerDebug;
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;
/**
* 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;
}
/* (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 = !hasContentBefore && hasContentAfter;
// 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 = null;
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
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);
}
try
{
// look for a transformer
transformerDebug.pushAvailable(reader.getContentUrl(), sourceMimetype, targetMimetype);
long sourceSize = reader.getSize();
List<ContentTransformer> transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options);
transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.transform(...)");
if (transformers.isEmpty())
{
throw new NoTransformerException(sourceMimetype, targetMimetype);
}
// we have a transformer, so do it
ContentTransformer transformer = transformers.size() == 0 ? null : transformers.get(0);
transformer.transform(reader, writer, options);
// done
}
finally
{
transformerDebug.popAvailable();
}
}
/**
* @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)
{
try
{
// look for a transformer
transformerDebug.pushAvailable(sourceUrl, sourceMimetype, targetMimetype);
List<ContentTransformer> transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options);
transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.getTransformer(...)");
return (transformers.isEmpty()) ? null : transformers.get(0);
}
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);
}
// look for a transformer
ContentTransformer transformer = transformerRegistry.getTransformer(sourceMimetype, reader.getSize(), targetMimetype, options);
return (transformer != null);
}
/**
* 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);
}
}
}
}