mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-14 17:58:59 +00:00
126543 jkaabimofrad: Merged FILE-FOLDER-API (5.2.0) to HEAD (5.2) 123868 jkaabimofrad: RA-856: Added support for REST APIs “/content” endpoints to optionally set their own cache. (Currently, only the Renditions API is setting its own cache) - Removed the http attachment header setting from the AbstractResourceWebScript, as the ContentStreamer already has this feature - Reformatted ContentStreamer according to Alfresco code standards git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@126888 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
454 lines
19 KiB
Java
454 lines
19 KiB
Java
/*
|
|
* Copyright (C) 2005-2016 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.rest.api.impl;
|
|
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.query.PagingResults;
|
|
import org.alfresco.repo.thumbnail.ThumbnailDefinition;
|
|
import org.alfresco.repo.thumbnail.ThumbnailHelper;
|
|
import org.alfresco.repo.thumbnail.ThumbnailRegistry;
|
|
import org.alfresco.repo.thumbnail.script.ScriptThumbnailService;
|
|
import org.alfresco.rest.antlr.WhereClauseParser;
|
|
import org.alfresco.rest.api.Nodes;
|
|
import org.alfresco.rest.api.Renditions;
|
|
import org.alfresco.rest.api.model.ContentInfo;
|
|
import org.alfresco.rest.api.model.Rendition;
|
|
import org.alfresco.rest.api.model.Rendition.RenditionStatus;
|
|
import org.alfresco.rest.framework.core.exceptions.ApiException;
|
|
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
|
|
import org.alfresco.rest.framework.core.exceptions.DisabledServiceException;
|
|
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
|
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
|
|
import org.alfresco.rest.framework.resource.content.BinaryResource;
|
|
import org.alfresco.rest.framework.resource.content.CacheDirective;
|
|
import org.alfresco.rest.framework.resource.content.ContentInfoImpl;
|
|
import org.alfresco.rest.framework.resource.content.FileBinaryResource;
|
|
import org.alfresco.rest.framework.resource.content.NodeBinaryResource;
|
|
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
|
import org.alfresco.rest.framework.resource.parameters.Paging;
|
|
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
|
import org.alfresco.rest.framework.resource.parameters.where.Query;
|
|
import org.alfresco.rest.framework.resource.parameters.where.QueryHelper;
|
|
import org.alfresco.rest.workflow.api.impl.MapBasedQueryWalker;
|
|
import org.alfresco.service.ServiceRegistry;
|
|
import org.alfresco.service.cmr.action.Action;
|
|
import org.alfresco.service.cmr.action.ActionService;
|
|
import org.alfresco.service.cmr.rendition.RenditionService;
|
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
import org.alfresco.service.cmr.repository.ContentData;
|
|
import org.alfresco.service.cmr.repository.MimetypeService;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.thumbnail.ThumbnailService;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.util.PropertyCheck;
|
|
import org.alfresco.util.TempFileProvider;
|
|
import org.apache.commons.lang.StringUtils;
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.springframework.context.ResourceLoaderAware;
|
|
import org.springframework.core.io.ResourceLoader;
|
|
|
|
import java.io.File;
|
|
import java.io.InputStream;
|
|
import java.io.Serializable;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.TreeMap;
|
|
|
|
/**
|
|
* @author Jamal Kaabi-Mofrad
|
|
*/
|
|
public class RenditionsImpl implements Renditions, ResourceLoaderAware
|
|
{
|
|
private static final Log LOGGER = LogFactory.getLog(RenditionsImpl.class);
|
|
|
|
private static final String PARAM_STATUS = "status";
|
|
private static final Set<String> RENDITION_STATUS_COLLECTION_EQUALS_QUERY_PROPERTIES = Collections.singleton(PARAM_STATUS);
|
|
|
|
private Nodes nodes;
|
|
private NodeService nodeService;
|
|
private ThumbnailService thumbnailService;
|
|
private ScriptThumbnailService scriptThumbnailService;
|
|
private RenditionService renditionService;
|
|
private MimetypeService mimetypeService;
|
|
private ActionService actionService;
|
|
private NamespaceService namespaceService;
|
|
private ServiceRegistry serviceRegistry;
|
|
private ResourceLoader resourceLoader;
|
|
|
|
public void setNodes(Nodes nodes)
|
|
{
|
|
this.nodes = nodes;
|
|
}
|
|
|
|
public void setThumbnailService(ThumbnailService thumbnailService)
|
|
{
|
|
this.thumbnailService = thumbnailService;
|
|
}
|
|
|
|
public void setScriptThumbnailService(ScriptThumbnailService scriptThumbnailService)
|
|
{
|
|
this.scriptThumbnailService = scriptThumbnailService;
|
|
}
|
|
|
|
public void setServiceRegistry(ServiceRegistry serviceRegistry)
|
|
{
|
|
this.serviceRegistry = serviceRegistry;
|
|
}
|
|
|
|
@Override
|
|
public void setResourceLoader(ResourceLoader resourceLoader)
|
|
{
|
|
this.resourceLoader = resourceLoader;
|
|
}
|
|
|
|
public void init()
|
|
{
|
|
PropertyCheck.mandatory(this, "nodes", nodes);
|
|
PropertyCheck.mandatory(this, "thumbnailService", thumbnailService);
|
|
PropertyCheck.mandatory(this, "scriptThumbnailService", scriptThumbnailService);
|
|
PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry);
|
|
|
|
this.nodeService = serviceRegistry.getNodeService();
|
|
this.actionService = serviceRegistry.getActionService();
|
|
this.renditionService = serviceRegistry.getRenditionService();
|
|
this.mimetypeService = serviceRegistry.getMimetypeService();
|
|
this.namespaceService = serviceRegistry.getNamespaceService();
|
|
}
|
|
|
|
@Override
|
|
public CollectionWithPagingInfo<Rendition> getRenditions(String nodeId, Parameters parameters)
|
|
{
|
|
final NodeRef nodeRef = validateSourceNode(nodeId);
|
|
String contentMimeType = getMimeType(nodeRef);
|
|
|
|
Query query = parameters.getQuery();
|
|
boolean includeCreated = true;
|
|
boolean includeNotCreated = true;
|
|
if (query != null)
|
|
{
|
|
// Filtering via "where" clause
|
|
MapBasedQueryWalker propertyWalker = new MapBasedQueryWalker(RENDITION_STATUS_COLLECTION_EQUALS_QUERY_PROPERTIES, null);
|
|
QueryHelper.walk(query, propertyWalker);
|
|
|
|
String withStatus = propertyWalker.getProperty(PARAM_STATUS, WhereClauseParser.EQUALS);
|
|
if (withStatus != null)
|
|
{
|
|
try
|
|
{
|
|
includeCreated = RenditionStatus.CREATED.equals(RenditionStatus.valueOf(withStatus));
|
|
}
|
|
catch (IllegalArgumentException ex)
|
|
{
|
|
throw new InvalidArgumentException("Invalid status value: " + withStatus);
|
|
}
|
|
includeNotCreated = !includeCreated;
|
|
}
|
|
}
|
|
|
|
Map<String, Rendition> apiRenditions = new TreeMap<>();
|
|
if (includeNotCreated)
|
|
{
|
|
// List all available thumbnail definitions
|
|
List<ThumbnailDefinition> thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(contentMimeType, -1);
|
|
for (ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions)
|
|
{
|
|
apiRenditions.put(thumbnailDefinition.getName(), toApiRendition(thumbnailDefinition));
|
|
}
|
|
}
|
|
|
|
List<ChildAssociationRef> nodeRefRenditions = renditionService.getRenditions(nodeRef);
|
|
if (!nodeRefRenditions.isEmpty())
|
|
{
|
|
for (ChildAssociationRef childAssociationRef : nodeRefRenditions)
|
|
{
|
|
NodeRef renditionNodeRef = childAssociationRef.getChildRef();
|
|
Rendition apiRendition = toApiRendition(renditionNodeRef);
|
|
if (includeCreated)
|
|
{
|
|
// Replace/append any thumbnail definitions with created rendition info
|
|
apiRenditions.put(apiRendition.getId(), apiRendition);
|
|
}
|
|
else
|
|
{
|
|
// Remove any thumbnail definitions that has been created from the list,
|
|
// as the filter requires only the Not_Created renditions
|
|
apiRenditions.remove(apiRendition.getId());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wrap paging info, as the core service doesn't support paging
|
|
Paging paging = parameters.getPaging();
|
|
PagingResults<Rendition> results = Util.wrapPagingResults(paging, apiRenditions.values());
|
|
|
|
return CollectionWithPagingInfo.asPaged(paging, results.getPage(), results.hasMoreItems(), results.getTotalResultCount().getFirst());
|
|
}
|
|
|
|
@Override
|
|
public Rendition getRendition(String nodeId, String renditionId, Parameters parameters)
|
|
{
|
|
final NodeRef nodeRef = validateSourceNode(nodeId);
|
|
NodeRef renditionNodeRef = getRenditionByName(nodeRef, renditionId, parameters);
|
|
|
|
// if there is no rendition, then try to find the available/registered rendition (yet to be created).
|
|
if (renditionNodeRef == null)
|
|
{
|
|
ThumbnailDefinition thumbnailDefinition = thumbnailService.getThumbnailRegistry().getThumbnailDefinition(renditionId);
|
|
if (thumbnailDefinition == null)
|
|
{
|
|
throw new NotFoundException(renditionId + " is not registered.");
|
|
}
|
|
else
|
|
{
|
|
String contentMimeType = getMimeType(nodeRef);
|
|
// List all available thumbnail definitions for the source node
|
|
List<ThumbnailDefinition> thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(contentMimeType, -1);
|
|
boolean found = false;
|
|
for (ThumbnailDefinition td : thumbnailDefinitions)
|
|
{
|
|
// Check the registered renditionId is applicable for the node's mimeType
|
|
if (renditionId.equals(td.getName()))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
throw new NotFoundException(renditionId + " is not applicable for the node's mimeType " + contentMimeType);
|
|
}
|
|
}
|
|
return toApiRendition(thumbnailDefinition);
|
|
}
|
|
|
|
return toApiRendition(renditionNodeRef);
|
|
}
|
|
|
|
@Override
|
|
public void createRendition(String nodeId, Rendition rendition, Parameters parameters)
|
|
{
|
|
// If thumbnail generation has been configured off, then don't bother.
|
|
if (!thumbnailService.getThumbnailsEnabled())
|
|
{
|
|
throw new DisabledServiceException("Thumbnail generation has been disabled.");
|
|
}
|
|
|
|
final NodeRef sourceNodeRef = validateSourceNode(nodeId);
|
|
final NodeRef renditionNodeRef = getRenditionByName(sourceNodeRef, rendition.getId(), parameters);
|
|
if (renditionNodeRef != null)
|
|
{
|
|
throw new ConstraintViolatedException(rendition.getId() + " rendition already exists.");
|
|
}
|
|
|
|
// Use the thumbnail registry to get the details of the thumbnail
|
|
ThumbnailRegistry registry = thumbnailService.getThumbnailRegistry();
|
|
ThumbnailDefinition thumbnailDefinition = registry.getThumbnailDefinition(rendition.getId());
|
|
if (thumbnailDefinition == null)
|
|
{
|
|
throw new NotFoundException(rendition.getId() + " is not registered.");
|
|
}
|
|
|
|
ContentData contentData = getContentData(sourceNodeRef, true);
|
|
// Check if anything is currently available to generate thumbnails for the specified mimeType
|
|
if (!registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), contentData.getMimetype(), contentData.getSize(), sourceNodeRef,
|
|
thumbnailDefinition))
|
|
{
|
|
throw new InvalidArgumentException("Unable to create thumbnail '" + thumbnailDefinition.getName() + "' for " +
|
|
contentData.getMimetype() + " as no transformer is currently available.");
|
|
}
|
|
|
|
Action action = ThumbnailHelper.createCreateThumbnailAction(thumbnailDefinition, serviceRegistry);
|
|
// Queue async creation of thumbnail
|
|
actionService.executeAction(action, sourceNodeRef, true, true);
|
|
}
|
|
|
|
@Override
|
|
public BinaryResource getContent(String nodeId, String renditionId, Parameters parameters)
|
|
{
|
|
final NodeRef sourceNodeRef = validateSourceNode(nodeId);
|
|
final NodeRef renditionNodeRef = getRenditionByName(sourceNodeRef, renditionId, parameters);
|
|
|
|
// By default set attachment header (with rendition Id) unless attachment=false
|
|
boolean attach = true;
|
|
String attachment = parameters.getParameter("attachment");
|
|
if (attachment != null)
|
|
{
|
|
attach = Boolean.valueOf(attachment);
|
|
}
|
|
final String attachFileName = (attach ? renditionId : null);
|
|
|
|
if (renditionNodeRef == null)
|
|
{
|
|
boolean isPlaceholder = Boolean.valueOf(parameters.getParameter("placeholder"));
|
|
if (!isPlaceholder)
|
|
{
|
|
throw new NotFoundException("Thumbnail was not found for [" + renditionId + ']');
|
|
}
|
|
String sourceNodeMimeType = getMimeType(sourceNodeRef);
|
|
// resource based on the content's mimeType and rendition id
|
|
String phPath = scriptThumbnailService.getMimeAwarePlaceHolderResourcePath(renditionId, sourceNodeMimeType);
|
|
if (phPath == null)
|
|
{
|
|
// 404 since no thumbnail was found
|
|
throw new NotFoundException("Thumbnail was not found and no placeholder resource available for [" + renditionId + ']');
|
|
}
|
|
else
|
|
{
|
|
if (LOGGER.isDebugEnabled())
|
|
{
|
|
LOGGER.debug("Retrieving content from resource path [" + phPath + ']');
|
|
}
|
|
// get extension of resource
|
|
String ext = "";
|
|
int extIndex = phPath.lastIndexOf('.');
|
|
if (extIndex != -1)
|
|
{
|
|
ext = phPath.substring(extIndex);
|
|
}
|
|
|
|
try
|
|
{
|
|
final String resourcePath = "classpath:" + phPath;
|
|
InputStream inputStream = resourceLoader.getResource(resourcePath).getInputStream();
|
|
// create temporary file
|
|
File file = TempFileProvider.createTempFile(inputStream, "RenditionsApi-", ext);
|
|
return new FileBinaryResource(file, attachFileName);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (LOGGER.isErrorEnabled())
|
|
{
|
|
LOGGER.error("Couldn't load the placeholder." + ex.getMessage());
|
|
}
|
|
new ApiException("Couldn't load the placeholder.");
|
|
}
|
|
}
|
|
}
|
|
|
|
Map<QName, Serializable> nodeProps = nodeService.getProperties(renditionNodeRef);
|
|
ContentData contentData = (ContentData) nodeProps.get(ContentModel.PROP_CONTENT);
|
|
Date modified = (Date) nodeProps.get(ContentModel.PROP_MODIFIED);
|
|
|
|
org.alfresco.rest.framework.resource.content.ContentInfo contentInfo = null;
|
|
if (contentData != null)
|
|
{
|
|
contentInfo = new ContentInfoImpl(contentData.getMimetype(), contentData.getEncoding(), contentData.getSize(), contentData.getLocale());
|
|
}
|
|
// add cache settings
|
|
CacheDirective cacheDirective = new CacheDirective.Builder()
|
|
.setNeverCache(false)
|
|
.setMustRevalidate(false)
|
|
.setLastModified(modified)
|
|
.setETag(modified != null ? Long.toString(modified.getTime()) : null)
|
|
.setMaxAge(Long.valueOf(31536000))// one year (in seconds)
|
|
.build();
|
|
|
|
return new NodeBinaryResource(renditionNodeRef, ContentModel.PROP_CONTENT, contentInfo, attachFileName, cacheDirective);
|
|
}
|
|
|
|
protected NodeRef getRenditionByName(NodeRef nodeRef, String renditionId, Parameters parameters)
|
|
{
|
|
if (StringUtils.isEmpty(renditionId))
|
|
{
|
|
throw new InvalidArgumentException("renditionId can't be null or empty.");
|
|
}
|
|
// Thumbnails have a cm: prefix.
|
|
QName renditionQName = QName.resolveToQName(namespaceService, renditionId);
|
|
|
|
ChildAssociationRef nodeRefRendition = renditionService.getRenditionByName(nodeRef, renditionQName);
|
|
if (nodeRefRendition == null)
|
|
{
|
|
return null;
|
|
}
|
|
return nodeRefRendition.getChildRef();
|
|
}
|
|
|
|
protected Rendition toApiRendition(NodeRef renditionNodeRef)
|
|
{
|
|
Rendition apiRendition = new Rendition();
|
|
|
|
String renditionName = (String) nodeService.getProperty(renditionNodeRef, ContentModel.PROP_NAME);
|
|
apiRendition.setId(renditionName);
|
|
|
|
ContentData contentData = getContentData(renditionNodeRef, false);
|
|
ContentInfo contentInfo = null;
|
|
if (contentData != null)
|
|
{
|
|
contentInfo = new ContentInfo(contentData.getMimetype(),
|
|
getMimeTypeDisplayName(contentData.getMimetype()),
|
|
contentData.getSize(),
|
|
contentData.getEncoding());
|
|
}
|
|
apiRendition.setContent(contentInfo);
|
|
apiRendition.setStatus(RenditionStatus.CREATED);
|
|
|
|
return apiRendition;
|
|
}
|
|
|
|
protected Rendition toApiRendition(ThumbnailDefinition thumbnailDefinition)
|
|
{
|
|
ContentInfo contentInfo = new ContentInfo(thumbnailDefinition.getMimetype(),
|
|
getMimeTypeDisplayName(thumbnailDefinition.getMimetype()), null, null);
|
|
Rendition apiRendition = new Rendition();
|
|
apiRendition.setId(thumbnailDefinition.getName());
|
|
apiRendition.setContent(contentInfo);
|
|
apiRendition.setStatus(RenditionStatus.NOT_CREATED);
|
|
|
|
return apiRendition;
|
|
}
|
|
|
|
protected NodeRef validateSourceNode(String nodeId)
|
|
{
|
|
final NodeRef nodeRef = nodes.validateNode(nodeId);
|
|
if (!nodes.isSubClass(nodeRef, ContentModel.PROP_CONTENT, false))
|
|
{
|
|
throw new InvalidArgumentException("Node id '" + nodeId + "' does not represent a file.");
|
|
}
|
|
return nodeRef;
|
|
}
|
|
|
|
private String getMimeTypeDisplayName(String mimeType)
|
|
{
|
|
return mimetypeService.getDisplaysByMimetype().get(mimeType);
|
|
}
|
|
|
|
private ContentData getContentData(NodeRef nodeRef, boolean validate)
|
|
{
|
|
ContentData contentData = (ContentData) nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
|
|
if (validate && !ContentData.hasContent(contentData))
|
|
{
|
|
throw new InvalidArgumentException("Node id '" + nodeRef.getId() + "' has no content.");
|
|
}
|
|
return contentData;
|
|
}
|
|
|
|
private String getMimeType(NodeRef nodeRef)
|
|
{
|
|
ContentData contentData = getContentData(nodeRef, true);
|
|
return contentData.getMimetype();
|
|
}
|
|
}
|