mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
38065: ALF-13725: yui-genXXX appears in the warning message while changing file name 38113: Merged BRANCHES/DEV/CLOUD1 to BRANCHES/DEV/V4.1-BUG-FIX 35857: Merged BRANCHES/DEV/THOR1 to BRANCHES/DEV/CLOUD1 33050: THOR-427: Decrease log level of 'Login Failed' 38117: Merged V4.1 to V4.1-BUG-FIX 38087: ALF-14725: Merged V3.4-BUG-FIX to V4.1 38086: ALF-14724: Upgrade to Java 1.6 u33 38118: Missing semicolons 38128: Merged V3.4-BUG-FIX to V4.1-BUG-FIX 37821: Fix for ALF-13658 - Direct access (SSO) to share user dashboard with external authentication fails and user is prompted to log on when browsing 37829: ALF-14340 CLONE: Alfresco crashes when viewing doclib / previewing - PDF with CMap This first part is general code (does not fix the specific issue) that could be merged to any branch. - TransformerDebug changes made to support investigation - Addition of 'supported mimetype transformations' to spring configuration (this is generally what people have thought EXPLICIT transformations were). - Tidy up of OpenOffice and JOD converters so that they don't say they can convert a mimetype to itself. There is a binary transformer that can just copy them. This also simplifies the Transformer debug output. 37831: ALF-14340 CLONE: Alfresco crashes when viewing doclib / previewing - PDF with CMap - missing file from last commit. 37832: ALF-14340 CLONE: Alfresco crashes when viewing doclib / previewing - PDF with CMap This second part that fixes the specific issue with reconfiguration of the transformers used. - Configuration of transformers changed to use ImageMagick with Ghostscript rather than PDFBox and PDFRenderer. The same transformations should be possible after this change. No combinations have been removed or added. - ImageMagick with Ghostscript now replaces both PDFBox and PDFRenderer for PDF to PNG. - A single or double ImageMagick with Ghostscript transformation (fails over to double when not possible with a single transformation) is now used by transformer.complex.PDF.Image - ImageMagick with Ghostscript rather than transformer.complex.PDF.Image now declares itself as having an EXPLICIT transformation for PDF to PNG. So normally there is no need to even look at the more complicated transformer.complex.PDF.Image unless it is included as a component of another transformer (there are 3). - PDFRenderer and 3rd party libs were only being used for PDF to PNG, so could be removed. NOT REMOVED AT THIS STAGE TO ALLOW CUSTOMISATION USE AND WORKAROUNDS OF ANY ISSUED THAT MIGHT BE FOUND. - PDFBox is still required for text to PDF, PDF to Text and metadata extraction. - Tidy up transformer.PdfBox.TextToPdf so that it does not declare csv to pdf and xml to pdf as EXPLICIT as this is the only transformer that can do it. 37834: ALF-10518 - Improvements to tagQuery.get.js for the Repository use case to remove the need to add //* PATH for CompanyHome query. 37835: Fix for ALF-14429 - Recently Modified dashlet takes up to 30 seconds to load after upgrade to Alfresco 3.4.6.23 37858: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 37592: Merged V3.4-BUG-FIX to V3.4 (3.4.10) 35103: Merged DEV to V3.4-BUG-FIX 37789: Merged V3.4-BUG-FIX to V3.4 37788: Second part of fix from Alex Mukha for ALF-11714 37841: ALF-14524: Merged PATCHES/V3.4.9 to V3.4 37840: ALF-14589: ALF-9861 breaks the JSON based error reporting for document library actions (by custom actions and code) - Fix by Mr Roast 37865: Merged DEV to V3.4-BUG-FIX (with corrections) 37845: ALF-13929: Error during processing of the 'show audit' template after upgrade New patch that updates show_audit.ftl to the newest version during upgrade 37877: Correction to corrections: 37865: Merged DEV to V3.4-BUG-FIX (with corrections) 37845: ALF-13929: Error during processing of the 'show audit' template after upgrade New patch that updates show_audit.ftl to the newest version during upgrade 37918: Merged DEV to V3.4-BUG-FIX 37548: ALF-11124: English language bundle properties have _en_US suffix Updated ant scripts to create properties files without _US suffix. 38090: ALF-14699: Merged DEV to V3.4-BUGFIX 38070: Search in Alfresco Explorer does not work 38124: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY) 38092: Merged V3.4-BUG-FIX to V3.4 38090: : Merged DEV to V3.4-BUGFIX 38070: ALF-14699: Search in Alfresco Explorer does not work git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@38135 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1106 lines
45 KiB
Java
1106 lines
45 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.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;
|
|
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 = !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 = 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)
|
|
{
|
|
if (done)
|
|
{
|
|
logger.error("Transformer succeeded after previous transformer failed.", e);
|
|
}
|
|
else if (!first)
|
|
{
|
|
logger.error("Transformer exception.", 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);
|
|
}
|
|
}
|
|
}
|
|
} |