diff --git a/config/alfresco/templates/webscripts/org/alfresco/cmis/content.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/cmis/content.get.desc.xml index be1ea72b51..7d29ae356e 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/cmis/content.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/cmis/content.get.desc.xml @@ -7,18 +7,18 @@ - /cmis/i/{id}/content{property}?a={attach?} - /cmis/s/{store}/i/{id}/content{property}?a={attach?} + /cmis/i/{id}/content{property}?a={attach?}&streamId={streamId?} + /cmis/s/{store}/i/{id}/content{property}?a={attach?}&streamId={streamId?} - /cmis/p{path}/content{property}?a={attach?} - /cmis/s/{store}/p{path}/content{property}?a={attach?} + /cmis/p{path}/content{property}?a={attach?}&streamId={streamId?} + /cmis/s/{store}/p{path}/content{property}?a={attach?}&streamId={streamId?} - /api/node/content{property}/{store_type}/{store_id}/{id}?a={attach?} - /api/path/content{property}/{store_type}/{store_id}/{path}?a={attach?} - /api/avmpath/content{property}/{store_id}/{avmpath}?a={attach?} - /api/node/{store_type}/{store_id}/{id}/content{property}?a={attach?} - /api/path/{store_type}/{store_id}/{path}/content{property}?a={attach?} + /api/node/content{property}/{store_type}/{store_id}/{id}?a={attach?}&streamId={streamId?} + /api/path/content{property}/{store_type}/{store_id}/{path}?a={attach?}&streamId={streamId?} + /api/avmpath/content{property}/{store_id}/{avmpath}?a={attach?}&streamId={streamId?} + /api/node/{store_type}/{store_id}/{id}/content{property}?a={attach?}&streamId={streamId?} + /api/path/{store_type}/{store_id}/{path}/content{property}?a={attach?}&streamId={streamId?} @@ -43,6 +43,10 @@ if true, force download of content as attachment false + + streamId + if provided, download the rendition of the content as identified by the stream id + argument diff --git a/config/alfresco/templates/webscripts/org/alfresco/cmis/lib/atomentry.lib.atom.ftl b/config/alfresco/templates/webscripts/org/alfresco/cmis/lib/atomentry.lib.atom.ftl index b73bc48019..a7d26770a1 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/cmis/lib/atomentry.lib.atom.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/cmis/lib/atomentry.lib.atom.ftl @@ -53,7 +53,9 @@ [@linksLib.linkstream node "enclosure"/] [@linksLib.linknodeedit node/] [@linksLib.linkstream node "edit-media"/] -[@documentCMISLinks node=node renditionfilter=renditionfilter/] +[@documentCMISLinks node=node/] +[#local renditionsMap=cmisrenditions(node, renditionfilter)/] +[@renditionLinks node renditionsMap/] ${xmldate(node.properties.created)} [@contentsummary node/] ${node.name?xml} @@ -65,12 +67,13 @@ [#if includeallowableactions][@allowableactions node/][/#if] [@relationships node includerelationships includeallowableactions propfilter/] [#if includeacl][@aclreport node/][/#if] +[@renditions renditionsMap/] ${node.name?xml} [/@entry] [/#macro] -[#macro documentCMISLinks node renditionfilter="cmis:none"] +[#macro documentCMISLinks node] [@linksLib.linkallowableactions node/] [@linksLib.linkrelationships node/] [@linksLib.linkpolicies node/] @@ -86,10 +89,6 @@ [/#if] [@linksLib.linktype node/] [@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] [#-- --] @@ -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=""] [@entry ns] ${node.properties.creator!""} -${node.id} [#-- TODO --] +[@contentstream node/] urn:uuid:${node.id} [@linksLib.linknodeself node/] [@linksLib.linknodeedit node/] @@ -205,19 +204,16 @@ [#if row.nodes?? && row.nodes?size == 1][#assign node = row.nodes?first/][/#if] [#if node??] ${node.properties.creator!""} -[#-- TODO: review if consistent with ATOM --] -[#if node.isDocument] - [@contentstream node/] -[#else] - ${node.id} [#-- TODO --] -[/#if] +[@contentstream node/] urn:uuid:${node.id} [@linksLib.linknodeself node/] [@linksLib.linknodeedit node/] [#if node.isDocument] [@linksLib.linkstream node "enclosure"/] [@linksLib.linkstream node "edit-media"/] - [@documentCMISLinks node=node renditionfilter=renditionfilter/] + [@documentCMISLinks node=node/] + [#assign renditionsMap=cmisrenditions(node, renditionfilter)/] + [@renditionLinks node renditionsMap/] [#else] [@folderCMISLinks node=node/] [/#if] @@ -254,6 +250,9 @@ [#if node??] [#if includeallowableactions][@allowableactions node/][/#if] [@relationships node includerelationships includeallowableactions/] +[#if node.isDocument] +[@renditions renditionsMap/] +[/#if] [/#if] [/@entry] @@ -284,7 +283,6 @@ [/@entry] [/#macro] - [#-- --] [#-- CMIS Properties --] [#-- --] @@ -463,6 +461,30 @@ [/#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] + + ${rendition.streamId} + ${rendition.mimeType} + [#if rendition.length??]${rendition.length?c}[#else]-1[/#if] + ${rendition.kind.label} + [#if rendition.title??]${rendition.title}[/#if] + [#if rendition.height??]${rendition.height?c}[/#if] + [#if rendition.width??]${rendition.width?c}[/#if] + +[/#list] +[/#macro] + [#-- --] [#-- 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] [#-- 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 --] [#macro contentstream node][/#macro] diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index 9e20351882..0f18bbd28e 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -310,7 +310,9 @@ + + diff --git a/source/java/org/alfresco/repo/web/scripts/content/ContentGet.java b/source/java/org/alfresco/repo/web/scripts/content/ContentGet.java index 8b42e7408f..6f9589d2fc 100644 --- a/source/java/org/alfresco/repo/web/scripts/content/ContentGet.java +++ b/source/java/org/alfresco/repo/web/scripts/content/ContentGet.java @@ -20,21 +20,34 @@ package org.alfresco.repo.web.scripts.content; import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; +import javax.servlet.ServletContext; import javax.servlet.http.HttpServletResponse; +import org.alfresco.cmis.CMISFilterNotValidException; 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.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.namespace.NamespaceService; 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.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.context.ServletContextAware; +import org.springframework.web.context.support.ServletContextResource; /** @@ -44,16 +57,27 @@ import org.apache.commons.logging.LogFactory; * * @author davidc */ -public class ContentGet extends StreamContent +public class ContentGet extends StreamContent implements ServletContextAware { // Logger @SuppressWarnings("unused") private static final Log logger = LogFactory.getLog(ContentGet.class); // Component dependencies + private ServletContext servletContext; private ReferenceFactory referenceFactory; + private DictionaryService dictionaryService; private NamespaceService namespaceService; - + private CMISRenditionService renditionService; + + /** + * @param + */ + public void setServletContext(ServletContext servletContext) + { + this.servletContext = servletContext; + } + /** * @param reference factory */ @@ -62,6 +86,14 @@ public class ContentGet extends StreamContent this.referenceFactory = referenceFactory; } + /** + * @param dictionaryService + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + /** * @param namespaceService */ @@ -69,6 +101,14 @@ public class ContentGet extends StreamContent { 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) @@ -95,26 +135,139 @@ public class ContentGet extends StreamContent throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to find " + reference.toString()); } - // determine content property - QName propertyQName = ContentModel.PROP_CONTENT; - String contentPart = templateVars.get("property"); - if (contentPart.length() > 0 && contentPart.charAt(0) == ';') - { - if (contentPart.length() < 2) - { - throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Content property malformed"); - } - String propertyName = contentPart.substring(1); - if (propertyName.length() > 0) - { - propertyQName = QName.createQName(propertyName, namespaceService); - } - } - // determine attachment boolean attach = Boolean.valueOf(req.getParameter("a")); - // Stream the content - streamContent(req, res, nodeRef, propertyQName, attach); + // 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; + String contentPart = templateVars.get("property"); + if (contentPart.length() > 0 && contentPart.charAt(0) == ';') + { + if (contentPart.length() < 2) + { + throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Content property malformed"); + } + String propertyName = contentPart.substring(1); + if (propertyName.length() > 0) + { + propertyQName = QName.createQName(propertyName, namespaceService); + } + } + + // Stream the content + 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 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()); + } + } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/content/StreamContent.java b/source/java/org/alfresco/repo/web/scripts/content/StreamContent.java index 111a910c43..46134c991e 100644 --- a/source/java/org/alfresco/repo/web/scripts/content/StreamContent.java +++ b/source/java/org/alfresco/repo/web/scripts/content/StreamContent.java @@ -489,22 +489,7 @@ public class StreamContent extends AbstractWebScript protected void streamContentImpl(WebScriptRequest req, WebScriptResponse res, ContentReader reader, boolean attach, Date modified, String eTag, String attachFileName) throws IOException { - // handle attachment - 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); - } + setAttachment(res, attach, attachFileName); // establish mimetype String mimetype = reader.getMimetype(); @@ -553,4 +538,31 @@ public class StreamContent extends AbstractWebScript 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); + } + } + }