From 0c1d2728fb35c8aad6260f36c51f1b08e11f76fe Mon Sep 17 00:00:00 2001 From: Roy Wetherall Date: Thu, 5 Jun 2008 10:07:26 +0000 Subject: [PATCH] Added stream content 'kind of' web script, modified ContentGet webscript to use common code, create GET thumbnail method based on stream content kind of web script git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@9395 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../thumbnail/thumbnail.delete.desc.xml | 4 +- .../thumbnail/thumbnail.get.desc.xml | 10 +- .../repository/thumbnail/thumbnail.get.js | 16 +- .../thumbnail/thumbnail.put.desc.xml | 4 +- .../thumbnail/thumbnails.get.desc.xml | 8 +- .../repository/thumbnail/thumbnails.post.js | 3 +- .../web-scripts-application-context.xml | 9 +- .../repo/web/scripts/BaseWebScriptTest.java | 9 +- .../repo/web/scripts/bean/ContentGet.java | 283 ------------ .../repo/web/scripts/content/ContentGet.java | 141 ++++++ .../web/scripts/content/StreamContent.java | 413 ++++++++++++++++++ .../thumbnail/ThumbnailServiceTest.java | 16 +- 12 files changed, 606 insertions(+), 310 deletions(-) delete mode 100644 source/java/org/alfresco/repo/web/scripts/bean/ContentGet.java create mode 100644 source/java/org/alfresco/repo/web/scripts/content/ContentGet.java create mode 100644 source/java/org/alfresco/repo/web/scripts/content/StreamContent.java diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.delete.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.delete.desc.xml index 714a790cd0..7bf202bfdc 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.delete.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.delete.desc.xml @@ -1,8 +1,8 @@ Thumbnails Delete a thumbnail for a content resource - /api/node/{store_type}/{store_id}/{id}/content{property?}/thumbnails/{thumbnailname} - /api/path/{store_type}/{store_id}/{id}/content{property?}/thumbnails/{thumbnailname} + /api/node/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname} + /api/path/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname} guest required diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.desc.xml index 73604772a5..b99309f950 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.desc.xml @@ -1,9 +1,9 @@ - + Thumbnails - Create a new thumbnail for a content resource - /api/node/{store_type}/{store_id}/{id}/content{property?}/thumbnails - /api/path/{store_type}/{store_id}/{id}/content{property?}/thumbnails - + Get a named thumbnail for a content resource + /api/node/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname} + /api/path/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname} + argument guest required \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.js b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.js index 6f6809752c..a6c5b0f676 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.js +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.js @@ -13,17 +13,25 @@ function main() } // Get the thumbnail name from the JSON content - var thumbnailName = pathSegments[8]; + var thumbnailName = url.templateArgs.thumbnailname; //pathSegments[pathSegments.length - 1]; // 404 if no thumbnail name found if (thumbnailName == null) { status.setCode(status.STATUS_NOT_FOUND, "Thumbnail name was not provided"); + return; } - // Get the thumbnail ... - - + // Get the thumbnail + var thumbnail = node.getThumbnail(thumbnailName); + if (thumbnail == null) + { + // 404 since no thumbnail was found + status.setCode(status.STATUS_NOT_FOUND, "Thumbnail was not found"); + } + + // Place the details of the thumbnail into the model, this will be used to stream the content to the client + model.contentNode = thumbnail; } main(); \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.put.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.put.desc.xml index 915a27edc8..c5ad92b752 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.put.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.put.desc.xml @@ -1,8 +1,8 @@ Thumbnails Update a thumbnail for a content resource - /api/node/{store_type}/{store_id}/{id}/content{property?}/thumbnails/{thumbnailname} - /api/path/{store_type}/{store_id}/{id}/content{property?}/thumbnails/{thumbnailname} + /api/node/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname} + /api/path/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname} guest required diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnails.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnails.get.desc.xml index b4f3b480da..ef63a79f36 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnails.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnails.get.desc.xml @@ -1,9 +1,9 @@ Thumbnails - Get a named thumbnail for a content resource - /api/node/{store_type}/{store_id}/{id}/content{property?}/thumbnails/{thumbnailname} - /api/path/{store_type}/{store_id}/{id}/content{property?}/thumbnails/{thumbnailname} - + Get the thumbnails for a content resource + /api/node/{store_type}/{store_id}/{id}/content{property}/thumbnails + /api/path/{store_type}/{store_id}/{id}/content{property}/thumbnails + guest required \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnails.post.js b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnails.post.js index 4880e3beb6..2cf65470ac 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnails.post.js +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnails.post.js @@ -33,8 +33,7 @@ function main() // Prep the model model.node = node; model.thumbnailName = thumbnailName; - model.thumbnail = thumbnail; - + model.thumbnail = thumbnail; } main(); \ No newline at end of file diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index ab67c59a3f..3b1e1091ff 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -187,7 +187,7 @@ - + @@ -196,6 +196,13 @@ + + + + + + + diff --git a/source/java/org/alfresco/repo/web/scripts/BaseWebScriptTest.java b/source/java/org/alfresco/repo/web/scripts/BaseWebScriptTest.java index f238d7dc5b..d8dbe96dac 100644 --- a/source/java/org/alfresco/repo/web/scripts/BaseWebScriptTest.java +++ b/source/java/org/alfresco/repo/web/scripts/BaseWebScriptTest.java @@ -131,11 +131,12 @@ public abstract class BaseWebScriptTest extends TestCase MockHttpServletResponse response = BaseWebScriptTest.getServer().submitRequest(method, url, new HashMap(), body, contentType); if (expectedStatus > 0 && expectedStatus != response.getStatus()) { - if (response.getStatus() == 500) - { + //if (response.getStatus() == 500) + //{ System.out.println(response.getContentAsString()); - } - fail("Expected status code " + expectedStatus + " , " + response.getStatus() + " was returned."); + //} + + fail("Expected status code " + expectedStatus + " , " + response.getStatus() + " was returned"); } return response; } diff --git a/source/java/org/alfresco/repo/web/scripts/bean/ContentGet.java b/source/java/org/alfresco/repo/web/scripts/bean/ContentGet.java deleted file mode 100644 index 877aaacb94..0000000000 --- a/source/java/org/alfresco/repo/web/scripts/bean/ContentGet.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2005-2007 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program 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 General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.repo.web.scripts.bean; - -import java.io.IOException; -import java.net.SocketException; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.Date; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.model.Repository; -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.MimetypeService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.URLEncoder; -import org.alfresco.web.scripts.AbstractWebScript; -import org.alfresco.web.scripts.Cache; -import org.alfresco.web.scripts.WebScriptException; -import org.alfresco.web.scripts.WebScriptRequest; -import org.alfresco.web.scripts.WebScriptResponse; -import org.alfresco.web.scripts.servlet.WebScriptServletRequest; -import org.alfresco.web.scripts.servlet.WebScriptServletResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - - -/** - * Content Retrieval Service - * - * Stream content from the Repository. - * - * @author davidc - */ -public class ContentGet extends AbstractWebScript -{ - // Logger - private static final Log logger = LogFactory.getLog(ContentGet.class); - - private static final String NODE_URL = "/api/node/content/{0}/{1}/{2}/{3}"; - - // Component dependencies - private Repository repository; - private NamespaceService namespaceService; - private PermissionService permissionService; - private NodeService nodeService; - private ContentService contentService; - private MimetypeService mimetypeService; - - /** - * @param repository - */ - public void setRepository(Repository repository) - { - this.repository = repository; - } - - /** - * @param namespaceService - */ - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - /** - * @param permissionService - */ - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } - - /** - * @param nodeService - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param contentService - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - /** - * @param mimetypeService - */ - public void setMimetypeService(MimetypeService mimetypeService) - { - this.mimetypeService = mimetypeService; - } - - - /* (non-Javadoc) - * @see org.alfresco.web.scripts.WebScript#execute(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) - */ - public void execute(WebScriptRequest req, WebScriptResponse res) - throws IOException - { - // NOTE: This web script must be executed in a HTTP Servlet environment - if (!(req instanceof WebScriptServletRequest)) - { - throw new WebScriptException("Content retrieval must be executed in HTTP Servlet environment"); - } - HttpServletRequest httpReq = ((WebScriptServletRequest)req).getHttpServletRequest(); - HttpServletResponse httpRes = ((WebScriptServletResponse)res).getHttpServletResponse(); - - // convert web script URL to node reference in Repository - String match = req.getServiceMatch().getPath(); - String[] matchParts = match.split("/"); - String extensionPath = req.getExtensionPath(); - String[] extParts = extensionPath == null ? new String[1] : extensionPath.split("/"); - String[] path = new String[extParts.length -1]; - System.arraycopy(extParts, 1, path, 0, extParts.length -1); - NodeRef nodeRef = repository.findNodeRef(matchParts[2], path); - if (nodeRef == null) - { - throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to find " + matchParts[2] + " reference " + Arrays.toString(path)); - } - - // determine content property - QName propertyQName = ContentModel.PROP_CONTENT; - String contentPart = extParts[0]; - 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")); - - if (logger.isDebugEnabled()) - logger.debug("Retrieving content from node ref " + nodeRef.toString() + " (property: " + propertyQName.toString() + ") (attach: " + attach + ")"); - - // check that the user has at least READ_CONTENT access - else redirect to the login page - if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED) - { - throw new WebScriptException(HttpServletResponse.SC_FORBIDDEN, "Permission denied"); - } - - // check If-Modified-Since header and set Last-Modified header as appropriate - Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); - long modifiedSince = httpReq.getDateHeader("If-Modified-Since"); - if (modifiedSince > 0L) - { - // round the date to the ignore millisecond value which is not supplied by header - long modDate = (modified.getTime() / 1000L) * 1000L; - if (modDate <= modifiedSince) - { - httpRes.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - return; - } - } - - // handle attachment - if (attach == true) - { - // 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 - httpRes.setHeader("Content-Disposition", "attachment"); - } - - // get the content reader - ContentReader reader = contentService.getReader(nodeRef, propertyQName); - if (reader == null || !reader.exists()) - { - throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to locate content for node ref " + nodeRef + " (property: " + propertyQName.toString() + ")"); - } - - // establish mimetype - String mimetype = reader.getMimetype(); - if (mimetype == null || mimetype.length() == 0) - { - mimetype = MimetypeMap.MIMETYPE_BINARY; - int extIndex = extensionPath.lastIndexOf('.'); - if (extIndex != -1) - { - String ext = extensionPath.substring(extIndex + 1); - String mt = mimetypeService.getMimetypesByExtension().get(ext); - if (mt != null) - { - mimetype = mt; - } - } - } - - // set mimetype for the content and the character encoding + length for the stream - httpRes.setContentType(mimetype); - httpRes.setCharacterEncoding(reader.getEncoding()); - httpRes.setHeader("Content-Length", Long.toString(reader.getSize())); - - // set caching - Cache cache = new Cache(); - cache.setNeverCache(false); - cache.setMustRevalidate(true); - cache.setLastModified(modified); - res.setCache(cache); - - // get the content and stream directly to the response output stream - // assuming the repository is capable of streaming in chunks, this should allow large files - // to be streamed directly to the browser response stream. - try - { - reader.getContent(res.getOutputStream()); - } - catch (SocketException e1) - { - // the client cut the connection - our mission was accomplished apart from a little error message - if (logger.isInfoEnabled()) - logger.info("Client aborted stream read:\n\tnode: " + nodeRef + "\n\tcontent: " + reader); - } - catch (ContentIOException e2) - { - if (logger.isInfoEnabled()) - logger.info("Client aborted stream read:\n\tnode: " + nodeRef + "\n\tcontent: " + reader); - } - } - - /** - * Helper to generate a URL to a content node for downloading content from the server. - * The content is supplied directly in the reponse. This generally means a browser will - * attempt to open the content directly if possible, else it will prompt to save the file. - * - * @param ref NodeRef of the content node to generate URL for (cannot be null) - * @param name File name end element to return on the url (used by the browser on Save) - * - * @return URL to download the content from the specified node - */ - public final static String generateNodeURL(NodeRef ref, String name) - { - return MessageFormat.format(NODE_URL, new Object[] { - ref.getStoreRef().getProtocol(), - ref.getStoreRef().getIdentifier(), - ref.getId(), - URLEncoder.encode(name) } ); - } -} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/content/ContentGet.java b/source/java/org/alfresco/repo/web/scripts/content/ContentGet.java new file mode 100644 index 0000000000..50a27b6039 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/content/ContentGet.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.scripts.content; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Arrays; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.model.Repository; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.URLEncoder; +import org.alfresco.web.scripts.WebScriptException; +import org.alfresco.web.scripts.WebScriptRequest; +import org.alfresco.web.scripts.WebScriptResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Content Retrieval Service + * + * Stream content from the Repository. + * + * @author davidc + */ +public class ContentGet extends StreamContent +{ + // Logger + @SuppressWarnings("unused") + private static final Log logger = LogFactory.getLog(ContentGet.class); + + private static final String NODE_URL = "/api/node/content/{0}/{1}/{2}/{3}"; + + // Component dependencies + private Repository repository; + private NamespaceService namespaceService; + + /** + * @param repository + */ + public void setRepository(Repository repository) + { + this.repository = repository; + } + + /** + * @param namespaceService + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @see org.alfresco.web.scripts.WebScript#execute(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) + */ + public void execute(WebScriptRequest req, WebScriptResponse res) + throws IOException + { + // convert web script URL to node reference in Repository + String match = req.getServiceMatch().getPath(); + String[] matchParts = match.split("/"); + String extensionPath = req.getExtensionPath(); + String[] extParts = extensionPath == null ? new String[1] : extensionPath.split("/"); + String[] path = new String[extParts.length -1]; + System.arraycopy(extParts, 1, path, 0, extParts.length -1); + NodeRef nodeRef = repository.findNodeRef(matchParts[2], path); + if (nodeRef == null) + { + throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to find " + matchParts[2] + " reference " + Arrays.toString(path)); + } + + // determine content property + QName propertyQName = ContentModel.PROP_CONTENT; + String contentPart = extParts[0]; + 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); + } + + /** + * Helper to generate a URL to a content node for downloading content from the server. + * The content is supplied directly in the reponse. This generally means a browser will + * attempt to open the content directly if possible, else it will prompt to save the file. + * + * @param ref NodeRef of the content node to generate URL for (cannot be null) + * @param name File name end element to return on the url (used by the browser on Save) + * + * @return URL to download the content from the specified node + */ + public final static String generateNodeURL(NodeRef ref, String name) + { + return MessageFormat.format(NODE_URL, new Object[] { + ref.getStoreRef().getProtocol(), + ref.getStoreRef().getIdentifier(), + ref.getId(), + URLEncoder.encode(name) } ); + } +} \ 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 new file mode 100644 index 0000000000..f73a0ba82d --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/content/StreamContent.java @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.scripts.content; + +import java.io.IOException; +import java.io.Writer; +import java.net.SocketException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +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.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.web.scripts.AbstractWebScript; +import org.alfresco.web.scripts.Cache; +import org.alfresco.web.scripts.Container; +import org.alfresco.web.scripts.Description; +import org.alfresco.web.scripts.ScriptContent; +import org.alfresco.web.scripts.Status; +import org.alfresco.web.scripts.WebScriptException; +import org.alfresco.web.scripts.WebScriptRequest; +import org.alfresco.web.scripts.WebScriptResponse; +import org.alfresco.web.scripts.WebScriptStatus; +import org.alfresco.web.scripts.servlet.WebScriptServletRequest; +import org.alfresco.web.scripts.servlet.WebScriptServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Web script 'type' that can be used when the binary data of a content property needs to be streamed back to the client + * as the result of executing the web script. + * + * @author Roy Wetherall + */ +public class StreamContent extends AbstractWebScript +{ + // Logger + private static final Log logger = LogFactory.getLog(StreamContent.class); + + protected PermissionService permissionService; + protected NodeService nodeService; + protected ContentService contentService; + protected MimetypeService mimetypeService; + + // Script Context + private String basePath; + private ScriptContent executeScript; + + /** + * @param mimetypeService + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + /** + * @param permissionService + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * @param nodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param contentService + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.AbstractWebScript#init(org.alfresco.web.scripts.WebScriptRegistry) + */ + @Override + public void init(Container container, Description description) + { + super.init(container, description); + + // Test for "execute" script + basePath = getDescription().getId(); + String scriptPath = basePath + ".js"; + executeScript = container.getScriptProcessor().findScript(scriptPath); + } + + /** + * @see org.alfresco.web.scripts.WebScript#execute(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) + */ + public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException + { + // retrieve requested format + String format = req.getFormat(); + + try + { + // construct model for script / template + Status status = new Status(); + Cache cache = new Cache(getDescription().getRequiredCache()); + Map model = executeImpl(req, status, cache); + if (model == null) + { + model = new HashMap(8, 1.0f); + } + model.put("status", status); + model.put("cache", cache); + + // execute script if it exists + if (executeScript != null) + { + if (logger.isDebugEnabled()) + logger.debug("Executing script " + executeScript.getPathDescription()); + + Map scriptModel = createScriptParameters(req, res, model); + // add return model allowing script to add items to template model + Map returnModel = new HashMap(8, 1.0f); + scriptModel.put("model", returnModel); + executeScript(executeScript, scriptModel); + mergeScriptModelIntoTemplateModel(returnModel, model); + } + + // Get the content parameters from the model + NodeRef nodeRef = (NodeRef)model.get("contentNode"); + if (nodeRef == null) + { + throw new WebScriptException("The content node was not specified so the content cannot be streamed to the client"); + } + QName propertyQName = null; + String contentProperty = (String)model.get("contentProperty"); + if (contentProperty == null) + { + // default to the standard content property + propertyQName = ContentModel.PROP_CONTENT; + } + else + { + propertyQName = QName.createQName(contentProperty); + } + Boolean attachBoolean = (Boolean)model.get("attach"); + boolean attach = false; + if (attachBoolean != null) + { + attach = attachBoolean.booleanValue(); + } + + // is a redirect to a status specific template required? + if (status.getRedirect()) + { + // create model for template rendering + Map templateModel = createTemplateParameters(req, res, model); + sendStatus(req, res, status, cache, format, templateModel); + } + else + { + // Stream the content + streamContent(req, res, nodeRef, propertyQName, attach); + } + } + catch(Throwable e) + { + if (logger.isInfoEnabled()) + logger.info("Caught exception & redirecting to status template: " + e.getMessage()); + + // extract status code, if specified + int statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + if (e instanceof WebScriptException) + { + statusCode = ((WebScriptException)e).getStatus(); + } + + // send status + Status status = new Status(); + status.setCode(statusCode); + status.setMessage(e.getMessage()); + status.setException(e); + Cache cache = new Cache(); + cache.setNeverCache(true); + Map customModel = new HashMap(8, 1.0f); + customModel.put("status", status); + Map templateModel = createTemplateParameters(req, res, customModel); + sendStatus(req, res, status, cache, format, templateModel); + } + } + + /** + * Merge script generated model into template-ready model + * + * @param scriptModel script model + * @param templateModel template model + */ + final private void mergeScriptModelIntoTemplateModel(Map scriptModel, Map templateModel) + { + for (Map.Entry entry : scriptModel.entrySet()) + { + // retrieve script model value + Object value = entry.getValue(); + Object templateValue = getContainer().getScriptProcessor().unwrapValue(value); + templateModel.put(entry.getKey(), templateValue); + } + } + + /** + * Execute custom Java logic + * + * @param req Web Script request + * @param status Web Script status + * @return custom service model + * @deprecated + */ + @SuppressWarnings("deprecation") + protected Map executeImpl(WebScriptRequest req, WebScriptStatus status) + { + return null; + } + + /** + * Execute custom Java logic + * + * @param req Web Script request + * @param status Web Script status + * @return custom service model + * @deprecated + */ + @SuppressWarnings("deprecation") + protected Map executeImpl(WebScriptRequest req, Status status) + { + return executeImpl(req, new WebScriptStatus(status)); + } + + /** + * Execute custom Java logic + * + * @param req Web Script request + * @param status Web Script status + * @param cache Web Script cache + * @return custom service model + */ + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) + { + // NOTE: Redirect to those web scripts implemented before cache support and v2.9 + return executeImpl(req, status); + } + + /** + * Render a template (of given format) to the Web Script Response + * + * @param format template format (null, default format) + * @param model data model to render + * @param writer where to output + */ + final protected void renderFormatTemplate(String format, Map model, Writer writer) + { + format = (format == null) ? "" : format; + String templatePath = basePath + "." + format + ".ftl"; + + if (logger.isDebugEnabled()) + logger.debug("Rendering template '" + templatePath + "'"); + + renderTemplate(templatePath, model, writer); + } + + /** + * Streams the content on a given node's content property to the response of the web script. + * + * @param req request + * @param res response + * @param nodeRef the node reference + * @param propertyQName the content property name + * @param attach indicates whether the content should be streamed as an attachment or not + * @throws IOException + */ + protected void streamContent(WebScriptRequest req, WebScriptResponse res, NodeRef nodeRef, QName propertyQName, boolean attach) + throws IOException + { + // NOTE: This web script must be executed in a HTTP Servlet environment + if (!(req instanceof WebScriptServletRequest)) + { + throw new WebScriptException("Content retrieval must be executed in HTTP Servlet environment"); + } + HttpServletRequest httpReq = ((WebScriptServletRequest)req).getHttpServletRequest(); + HttpServletResponse httpRes = ((WebScriptServletResponse)res).getHttpServletResponse(); + + if (logger.isDebugEnabled()) + logger.debug("Retrieving content from node ref " + nodeRef.toString() + " (property: " + propertyQName.toString() + ") (attach: " + attach + ")"); + + // check that the user has at least READ_CONTENT access - else redirect to the login page + if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED) + { + throw new WebScriptException(HttpServletResponse.SC_FORBIDDEN, "Permission denied"); + } + + // check If-Modified-Since header and set Last-Modified header as appropriate + Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); + long modifiedSince = httpReq.getDateHeader("If-Modified-Since"); + if (modifiedSince > 0L) + { + // round the date to the ignore millisecond value which is not supplied by header + long modDate = (modified.getTime() / 1000L) * 1000L; + if (modDate <= modifiedSince) + { + httpRes.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + return; + } + } + + // handle attachment + if (attach == true) + { + // 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 + httpRes.setHeader("Content-Disposition", "attachment"); + } + + // get the content reader + ContentReader reader = contentService.getReader(nodeRef, propertyQName); + if (reader == null || !reader.exists()) + { + throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to locate content for node ref " + nodeRef + " (property: " + propertyQName.toString() + ")"); + } + + // establish mimetype + String mimetype = reader.getMimetype(); + String extensionPath = req.getExtensionPath(); + if (mimetype == null || mimetype.length() == 0) + { + mimetype = MimetypeMap.MIMETYPE_BINARY; + int extIndex = extensionPath.lastIndexOf('.'); + if (extIndex != -1) + { + String ext = extensionPath.substring(extIndex + 1); + String mt = mimetypeService.getMimetypesByExtension().get(ext); + if (mt != null) + { + mimetype = mt; + } + } + } + + // set mimetype for the content and the character encoding + length for the stream + httpRes.setContentType(mimetype); + httpRes.setCharacterEncoding(reader.getEncoding()); + httpRes.setHeader("Content-Length", Long.toString(reader.getSize())); + + // set caching + Cache cache = new Cache(); + cache.setNeverCache(false); + cache.setMustRevalidate(true); + cache.setLastModified(modified); + res.setCache(cache); + + // get the content and stream directly to the response output stream + // assuming the repository is capable of streaming in chunks, this should allow large files + // to be streamed directly to the browser response stream. + try + { + reader.getContent(res.getOutputStream()); + } + catch (SocketException e1) + { + // the client cut the connection - our mission was accomplished apart from a little error message + if (logger.isInfoEnabled()) + logger.info("Client aborted stream read:\n\tnode: " + nodeRef + "\n\tcontent: " + reader); + } + catch (ContentIOException e2) + { + if (logger.isInfoEnabled()) + logger.info("Client aborted stream read:\n\tnode: " + nodeRef + "\n\tcontent: " + reader); + } + } + +} diff --git a/source/java/org/alfresco/repo/web/scripts/thumbnail/ThumbnailServiceTest.java b/source/java/org/alfresco/repo/web/scripts/thumbnail/ThumbnailServiceTest.java index 84a5ea13e2..e95beb9d7b 100644 --- a/source/java/org/alfresco/repo/web/scripts/thumbnail/ThumbnailServiceTest.java +++ b/source/java/org/alfresco/repo/web/scripts/thumbnail/ThumbnailServiceTest.java @@ -105,17 +105,27 @@ public class ThumbnailServiceTest extends BaseWebScriptTest if (this.contentService.getTransformer(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_FLASH) != null) { String url = "/api/node/" + pdfNode.getStoreRef().getProtocol() + "/" + pdfNode.getStoreRef().getIdentifier() + "/" + pdfNode.getId() + "/content/thumbnails"; - System.out.println(url); JSONObject tn = new JSONObject(); tn.put("thumbnailName", "webpreview"); - System.out.println(tn.toString()); MockHttpServletResponse response = this.postRequest(url, 200, tn.toString(), "application/json"); - //JSONObject result = new JSONObject(response.getContentAsString()); System.out.println(response.getContentAsString()); } + + // Do a image transformation (medium) + String url = "/api/node/" + jpgNode.getStoreRef().getProtocol() + "/" + jpgNode.getStoreRef().getIdentifier() + "/" + jpgNode.getId() + "/content/thumbnails"; + JSONObject tn = new JSONObject(); + tn.put("thumbnailName", "medium"); + MockHttpServletResponse response = this.postRequest(url, 200, tn.toString(), "application/json"); + System.out.println(response.getContentAsString()); + JSONObject result = new JSONObject(response.getContentAsString()); + String thumbnailUrl = result.getString("url").substring(17); + + System.out.println(thumbnailUrl); + response = getRequest(thumbnailUrl, 200); + }