Fix ALF-2728: AtomPub renditions are not rendered as part of cmis:object, although their rel links are.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@20125 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
David Caruana
2010-05-08 10:07:50 +00:00
parent 44c336dd50
commit d3565f67de
5 changed files with 256 additions and 63 deletions

View File

@@ -7,18 +7,18 @@
</description> </description>
<!-- by object id --> <!-- by object id -->
<url>/cmis/i/{id}/content{property}?a={attach?}</url> <url>/cmis/i/{id}/content{property}?a={attach?}&amp;streamId={streamId?}</url>
<url>/cmis/s/{store}/i/{id}/content{property}?a={attach?}</url> <url>/cmis/s/{store}/i/{id}/content{property}?a={attach?}&amp;streamId={streamId?}</url>
<!-- by path --> <!-- by path -->
<url>/cmis/p{path}/content{property}?a={attach?}</url> <url>/cmis/p{path}/content{property}?a={attach?}&amp;streamId={streamId?}</url>
<url>/cmis/s/{store}/p{path}/content{property}?a={attach?}</url> <url>/cmis/s/{store}/p{path}/content{property}?a={attach?}&amp;streamId={streamId?}</url>
<!-- alfresco style --> <!-- alfresco style -->
<url>/api/node/content{property}/{store_type}/{store_id}/{id}?a={attach?}</url> <url>/api/node/content{property}/{store_type}/{store_id}/{id}?a={attach?}&amp;streamId={streamId?}</url>
<url>/api/path/content{property}/{store_type}/{store_id}/{path}?a={attach?}</url> <url>/api/path/content{property}/{store_type}/{store_id}/{path}?a={attach?}&amp;streamId={streamId?}</url>
<url>/api/avmpath/content{property}/{store_id}/{avmpath}?a={attach?}</url> <url>/api/avmpath/content{property}/{store_id}/{avmpath}?a={attach?}&amp;streamId={streamId?}</url>
<url>/api/node/{store_type}/{store_id}/{id}/content{property}?a={attach?}</url> <url>/api/node/{store_type}/{store_id}/{id}/content{property}?a={attach?}&amp;streamId={streamId?}</url>
<url>/api/path/{store_type}/{store_id}/{path}/content{property}?a={attach?}</url> <url>/api/path/{store_type}/{store_id}/{path}/content{property}?a={attach?}&amp;streamId={streamId?}</url>
<args> <args>
<arg> <arg>
@@ -43,6 +43,10 @@
<description>if true, force download of content as attachment</description> <description>if true, force download of content as attachment</description>
<default>false</default> <default>false</default>
</arg> </arg>
<arg>
<shortname>streamId</shortname>
<description>if provided, download the rendition of the content as identified by the stream id</description>
</arg>
</args> </args>
<format default="">argument</format> <format default="">argument</format>

View File

@@ -53,7 +53,9 @@
[@linksLib.linkstream node "enclosure"/] [@linksLib.linkstream node "enclosure"/]
[@linksLib.linknodeedit node/] [@linksLib.linknodeedit node/]
[@linksLib.linkstream node "edit-media"/] [@linksLib.linkstream node "edit-media"/]
[@documentCMISLinks node=node renditionfilter=renditionfilter/] [@documentCMISLinks node=node/]
[#local renditionsMap=cmisrenditions(node, renditionfilter)/]
[@renditionLinks node renditionsMap/]
<published>${xmldate(node.properties.created)}</published> <published>${xmldate(node.properties.created)}</published>
<summary>[@contentsummary node/]</summary> <summary>[@contentsummary node/]</summary>
<title>${node.name?xml}</title> <title>${node.name?xml}</title>
@@ -65,12 +67,13 @@
[#if includeallowableactions][@allowableactions node/][/#if] [#if includeallowableactions][@allowableactions node/][/#if]
[@relationships node includerelationships includeallowableactions propfilter/] [@relationships node includerelationships includeallowableactions propfilter/]
[#if includeacl][@aclreport node/][/#if] [#if includeacl][@aclreport node/][/#if]
[@renditions renditionsMap/]
</cmisra:object> </cmisra:object>
<cmisra:pathSegment>${node.name?xml}</cmisra:pathSegment> <cmisra:pathSegment>${node.name?xml}</cmisra:pathSegment>
[/@entry] [/@entry]
[/#macro] [/#macro]
[#macro documentCMISLinks node renditionfilter="cmis:none"] [#macro documentCMISLinks node]
[@linksLib.linkallowableactions node/] [@linksLib.linkallowableactions node/]
[@linksLib.linkrelationships node/] [@linksLib.linkrelationships node/]
[@linksLib.linkpolicies node/] [@linksLib.linkpolicies node/]
@@ -86,10 +89,6 @@
[/#if] [/#if]
[@linksLib.linktype node/] [@linksLib.linktype node/]
[@linksLib.linkservice/] [@linksLib.linkservice/]
[#local nodeMap=cmisrenditions(node, renditionfilter)/]
[#list nodeMap.renditions as rendition]
[@linksLib.linkrendition node=node rendition=rendition renditionNode=nodeMap.renditionNodes[rendition_index]/]
[/#list]
[/#macro] [/#macro]
[#-- --] [#-- --]
@@ -103,7 +102,7 @@
[#macro folder node renditionfilter="cmis:none" propfilter="*" typesfilter="any" includeallowableactions=false includerelationships="none" includeacl=false ns="" depth=1 maxdepth=1 relativePathSegment="" nestedkind=""] [#macro folder node renditionfilter="cmis:none" propfilter="*" typesfilter="any" includeallowableactions=false includerelationships="none" includeacl=false ns="" depth=1 maxdepth=1 relativePathSegment="" nestedkind=""]
[@entry ns] [@entry ns]
<author><name>${node.properties.creator!""}</name></author> <author><name>${node.properties.creator!""}</name></author>
<content>${node.id}</content> [#-- TODO --] [@contentstream node/]
<id>urn:uuid:${node.id}</id> <id>urn:uuid:${node.id}</id>
[@linksLib.linknodeself node/] [@linksLib.linknodeself node/]
[@linksLib.linknodeedit node/] [@linksLib.linknodeedit node/]
@@ -205,19 +204,16 @@
[#if row.nodes?? && row.nodes?size == 1][#assign node = row.nodes?first/][/#if] [#if row.nodes?? && row.nodes?size == 1][#assign node = row.nodes?first/][/#if]
[#if node??] [#if node??]
<author><name>${node.properties.creator!""}</name></author> <author><name>${node.properties.creator!""}</name></author>
[#-- TODO: review if consistent with ATOM --] [@contentstream node/]
[#if node.isDocument]
[@contentstream node/]
[#else]
<content>${node.id}</content> [#-- TODO --]
[/#if]
<id>urn:uuid:${node.id}</id> <id>urn:uuid:${node.id}</id>
[@linksLib.linknodeself node/] [@linksLib.linknodeself node/]
[@linksLib.linknodeedit node/] [@linksLib.linknodeedit node/]
[#if node.isDocument] [#if node.isDocument]
[@linksLib.linkstream node "enclosure"/] [@linksLib.linkstream node "enclosure"/]
[@linksLib.linkstream node "edit-media"/] [@linksLib.linkstream node "edit-media"/]
[@documentCMISLinks node=node renditionfilter=renditionfilter/] [@documentCMISLinks node=node/]
[#assign renditionsMap=cmisrenditions(node, renditionfilter)/]
[@renditionLinks node renditionsMap/]
[#else] [#else]
[@folderCMISLinks node=node/] [@folderCMISLinks node=node/]
[/#if] [/#if]
@@ -254,6 +250,9 @@
[#if node??] [#if node??]
[#if includeallowableactions][@allowableactions node/][/#if] [#if includeallowableactions][@allowableactions node/][/#if]
[@relationships node includerelationships includeallowableactions/] [@relationships node includerelationships includeallowableactions/]
[#if node.isDocument]
[@renditions renditionsMap/]
[/#if]
[/#if] [/#if]
</cmisra:object> </cmisra:object>
[/@entry] [/@entry]
@@ -284,7 +283,6 @@
[/@entry] [/@entry]
[/#macro] [/#macro]
[#-- --] [#-- --]
[#-- CMIS Properties --] [#-- CMIS Properties --]
[#-- --] [#-- --]
@@ -463,6 +461,30 @@
</cmis:permission> </cmis:permission>
[/#macro] [/#macro]
[#-- --]
[#-- Renditions --]
[#-- --]
[#macro renditionLinks node renditionsMap]
[#list renditionsMap.renditions as rendition]
[@linksLib.linkrendition node=node rendition=rendition renditionNode=renditionsMap.renditionNodes[rendition_index]/]
[/#list]
[/#macro]
[#macro renditions renditionsMap]
[#list renditionsMap.renditions as rendition]
<cmis:rendition>
<cmis:streamId>${rendition.streamId}</cmis:streamId>
<cmis:mimetype>${rendition.mimeType}</cmis:mimetype>
<cmis:length>[#if rendition.length??]${rendition.length?c}[#else]-1[/#if]</cmis:length>
<cmis:kind>${rendition.kind.label}</cmis:kind>
[#if rendition.title??]<cmis:title>${rendition.title}</cmis:title>[/#if]
[#if rendition.height??]<cmis:height>${rendition.height?c}</cmis:height>[/#if]
[#if rendition.width??]<cmis:width>${rendition.width?c}</cmis:width>[/#if]
</cmis:rendition>
[/#list]
[/#macro]
[#-- --] [#-- --]
[#-- ATOM Entry for Type Definition --] [#-- ATOM Entry for Type Definition --]
[#-- --] [#-- --]
@@ -769,7 +791,7 @@
[#macro foldersummary node][#if node.properties.description??]${node.properties.description?xml}[#elseif node.properties.title??]${node.properties.title?xml}[#else][/#if][/#macro] [#macro foldersummary node][#if node.properties.description??]${node.properties.description?xml}[#elseif node.properties.title??]${node.properties.title?xml}[#else][/#if][/#macro]
[#-- Helper to render Alfresco content type to Atom content type --] [#-- Helper to render Alfresco content type to Atom content type --]
[#macro contenttype type][#if type == "text/html"]text[#elseif type == "text/xhtml"]xhtml[#elseif type == "text/plain"]text<#else>${type}[/#if][/#macro] [#macro contenttype type][#if type == "text/html"]text[#elseif type == "text/xhtml"]xhtml[#elseif type == "text/plain"]text[#else]${type}[/#if][/#macro]
[#-- Helper to render atom content element --] [#-- Helper to render atom content element --]
[#macro contentstream node]<content[#if node.mimetype??] type="${node.mimetype}"[/#if] src="[@linksLib.contenturi node/]"/>[/#macro] [#macro contentstream node]<content[#if node.mimetype??] type="${node.mimetype}"[/#if] src="[@linksLib.contenturi node/]"/>[/#macro]

View File

@@ -310,7 +310,9 @@
<property name="permissionService" ref="PermissionService" /> <property name="permissionService" ref="PermissionService" />
<property name="nodeService" ref="NodeService" /> <property name="nodeService" ref="NodeService" />
<property name="contentService" ref="ContentService" /> <property name="contentService" ref="ContentService" />
<property name="dictionaryService" ref="DictionaryService" />
<property name="mimetypeService" ref="MimetypeService" /> <property name="mimetypeService" ref="MimetypeService" />
<property name="CMISRenditionService" ref="CMISRenditionService" />
</bean> </bean>
<!-- Content Write --> <!-- Content Write -->

View File

@@ -20,21 +20,34 @@ package org.alfresco.repo.web.scripts.content;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.alfresco.cmis.CMISFilterNotValidException;
import org.alfresco.cmis.CMISObjectReference; import org.alfresco.cmis.CMISObjectReference;
import org.alfresco.cmis.CMISRendition;
import org.alfresco.cmis.CMISRenditionService;
import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.cmis.reference.ReferenceFactory; import org.alfresco.repo.cmis.reference.ReferenceFactory;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.web.scripts.FileTypeImageUtils;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.FileTypeImageSize;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse; import org.springframework.extensions.webscripts.WebScriptResponse;
import org.apache.commons.logging.Log; import org.springframework.util.FileCopyUtils;
import org.apache.commons.logging.LogFactory; import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.support.ServletContextResource;
/** /**
@@ -44,15 +57,26 @@ import org.apache.commons.logging.LogFactory;
* *
* @author davidc * @author davidc
*/ */
public class ContentGet extends StreamContent public class ContentGet extends StreamContent implements ServletContextAware
{ {
// Logger // Logger
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final Log logger = LogFactory.getLog(ContentGet.class); private static final Log logger = LogFactory.getLog(ContentGet.class);
// Component dependencies // Component dependencies
private ServletContext servletContext;
private ReferenceFactory referenceFactory; private ReferenceFactory referenceFactory;
private DictionaryService dictionaryService;
private NamespaceService namespaceService; private NamespaceService namespaceService;
private CMISRenditionService renditionService;
/**
* @param
*/
public void setServletContext(ServletContext servletContext)
{
this.servletContext = servletContext;
}
/** /**
* @param reference factory * @param reference factory
@@ -62,6 +86,14 @@ public class ContentGet extends StreamContent
this.referenceFactory = referenceFactory; this.referenceFactory = referenceFactory;
} }
/**
* @param dictionaryService
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/** /**
* @param namespaceService * @param namespaceService
*/ */
@@ -70,6 +102,14 @@ public class ContentGet extends StreamContent
this.namespaceService = namespaceService; this.namespaceService = namespaceService;
} }
/**
* @param renditionService
*/
public void setCMISRenditionService(CMISRenditionService renditionService)
{
this.renditionService = renditionService;
}
/** /**
* @see org.alfresco.web.scripts.WebScript#execute(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) * @see org.alfresco.web.scripts.WebScript#execute(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse)
*/ */
@@ -95,7 +135,19 @@ public class ContentGet extends StreamContent
throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to find " + reference.toString()); throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to find " + reference.toString());
} }
// determine content property // determine attachment
boolean attach = Boolean.valueOf(req.getParameter("a"));
// stream content on node, or rendition of node
String streamId = req.getParameter("streamId");
if (streamId != null && streamId.length() > 0)
{
// render content rendition
streamRendition(req, res, reference, streamId, attach);
}
else
{
// render content
QName propertyQName = ContentModel.PROP_CONTENT; QName propertyQName = ContentModel.PROP_CONTENT;
String contentPart = templateVars.get("property"); String contentPart = templateVars.get("property");
if (contentPart.length() > 0 && contentPart.charAt(0) == ';') if (contentPart.length() > 0 && contentPart.charAt(0) == ';')
@@ -111,10 +163,111 @@ public class ContentGet extends StreamContent
} }
} }
// determine attachment
boolean attach = Boolean.valueOf(req.getParameter("a"));
// Stream the content // Stream the content
streamContent(req, res, nodeRef, propertyQName, attach); streamContent(req, res, nodeRef, propertyQName, attach);
} }
}
/**
* Stream content rendition
*
* @param req
* @param res
* @param reference
* @param streamId
* @param attach
* @throws IOException
*/
private void streamRendition(WebScriptRequest req, WebScriptResponse res, CMISObjectReference reference, String streamId, boolean attach)
throws IOException
{
try
{
// find rendition
CMISRendition rendition = null;
List<CMISRendition> renditions = renditionService.getRenditions(reference.getNodeRef(), "*");
for (CMISRendition candidateRendition : renditions)
{
if (candidateRendition.getStreamId().equals(streamId))
{
rendition = candidateRendition;
break;
}
}
if (rendition == null)
{
throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to find rendition " + streamId + " for " + reference.toString());
}
// determine if special case for icons
if (streamId.startsWith("alf:icon"))
{
streamIcon(res, reference, streamId, attach);
}
else
{
streamContent(req, res, rendition.getNodeRef(), ContentModel.PROP_CONTENT, attach);
}
}
catch(CMISFilterNotValidException e)
{
throw new WebScriptException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid Rendition Filter");
}
}
/**
* Stream Icon
*
* @param res
* @param reference
* @param streamId
* @param attach
* @throws IOException
*/
private void streamIcon(WebScriptResponse res, CMISObjectReference reference, String streamId, boolean attach)
throws IOException
{
// convert stream id to icon size
FileTypeImageSize imageSize = streamId.equals("alf:icon16") ? FileTypeImageSize.Small : FileTypeImageSize.Medium;
String iconSize = streamId.equals("alf:icon16") ? "-16" : "";
// calculate icon file name and path
String iconPath = null;
if (dictionaryService.isSubClass(nodeService.getType(reference.getNodeRef()), ContentModel.TYPE_CONTENT))
{
String name = (String)nodeService.getProperty(reference.getNodeRef(), ContentModel.PROP_NAME);
iconPath = FileTypeImageUtils.getFileTypeImage(servletContext, name, imageSize);
}
else
{
String icon = (String)nodeService.getProperty(reference.getNodeRef(), ApplicationModel.PROP_ICON);
if (icon != null)
{
iconPath = "/images/icons/" + icon + iconSize + ".gif";
}
else
{
iconPath = "/images/icons/space-icon-default" + iconSize + ".gif";
}
}
// set mimetype
String mimetype = MimetypeMap.MIMETYPE_BINARY;
int extIndex = iconPath.lastIndexOf('.');
if (extIndex != -1)
{
String ext = iconPath.substring(extIndex + 1);
mimetype = mimetypeService.getMimetype(ext);
}
res.setContentType(mimetype);
// stream icon
ServletContextResource resource = new ServletContextResource(servletContext, iconPath);
if (!resource.exists())
{
throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to find rendition " + streamId + " for " + reference.toString());
}
FileCopyUtils.copy(resource.getInputStream(), res.getOutputStream());
}
} }

View File

@@ -489,22 +489,7 @@ public class StreamContent extends AbstractWebScript
protected void streamContentImpl(WebScriptRequest req, WebScriptResponse res, ContentReader reader, boolean attach, protected void streamContentImpl(WebScriptRequest req, WebScriptResponse res, ContentReader reader, boolean attach,
Date modified, String eTag, String attachFileName) throws IOException Date modified, String eTag, String attachFileName) throws IOException
{ {
// handle attachment setAttachment(res, attach, attachFileName);
if (attach == true)
{
String headerValue = "attachment";
if (attachFileName != null && attachFileName.length() > 0)
{
if (logger.isDebugEnabled())
logger.debug("Attaching content using filename: " + attachFileName);
headerValue += "; filename=" + attachFileName;
}
// set header based on filename - will force a Save As from the browse if it doesn't recognize it
// this is better than the default response of the browser trying to display the contents
res.setHeader("Content-Disposition", headerValue);
}
// establish mimetype // establish mimetype
String mimetype = reader.getMimetype(); String mimetype = reader.getMimetype();
@@ -553,4 +538,31 @@ public class StreamContent extends AbstractWebScript
logger.info("Client aborted stream read:\n\tcontent: " + reader); logger.info("Client aborted stream read:\n\tcontent: " + reader);
} }
} }
/**
* Set attachment header
*
* @param res
* @param attach
* @param attachFileName
*/
protected void setAttachment(WebScriptResponse res, boolean attach, String attachFileName)
{
if (attach == true)
{
String headerValue = "attachment";
if (attachFileName != null && attachFileName.length() > 0)
{
if (logger.isDebugEnabled())
logger.debug("Attaching content using filename: " + attachFileName);
headerValue += "; filename=" + attachFileName;
}
// set header based on filename - will force a Save As from the browse if it doesn't recognize it
// this is better than the default response of the browser trying to display the contents
res.setHeader("Content-Disposition", headerValue);
}
}
} }