KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > web > app > servlet > DownloadContentServlet


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.web.app.servlet;
18
19 import java.io.IOException JavaDoc;
20 import java.io.UnsupportedEncodingException JavaDoc;
21 import java.net.SocketException JavaDoc;
22 import java.net.URLDecoder JavaDoc;
23 import java.net.URLEncoder JavaDoc;
24 import java.text.MessageFormat JavaDoc;
25 import java.util.StringTokenizer JavaDoc;
26
27 import javax.servlet.ServletException JavaDoc;
28 import javax.servlet.http.HttpServlet JavaDoc;
29 import javax.servlet.http.HttpServletRequest JavaDoc;
30 import javax.servlet.http.HttpServletResponse JavaDoc;
31
32 import org.alfresco.error.AlfrescoRuntimeException;
33 import org.alfresco.model.ContentModel;
34 import org.alfresco.repo.content.filestore.FileContentReader;
35 import org.alfresco.service.ServiceRegistry;
36 import org.alfresco.service.cmr.repository.ContentReader;
37 import org.alfresco.service.cmr.repository.ContentService;
38 import org.alfresco.service.cmr.repository.MimetypeService;
39 import org.alfresco.service.cmr.repository.NodeRef;
40 import org.alfresco.service.cmr.repository.StoreRef;
41 import org.alfresco.service.cmr.security.AccessStatus;
42 import org.alfresco.service.cmr.security.PermissionService;
43 import org.alfresco.service.namespace.QName;
44 import org.alfresco.web.app.Application;
45 import org.alfresco.web.ui.common.Utils;
46 import org.apache.commons.logging.Log;
47 import org.apache.commons.logging.LogFactory;
48
49 /**
50  * Servlet responsible for streaming node content from the repo directly to the response stream.
51  * The appropriate mimetype is calculated based on filename extension.
52  * <p>
53  * The URL to the servlet should be generated thus:
54  * <pre>/alfresco/download/attach/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf</pre>
55  * or
56  * <pre>/alfresco/download/direct/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf</pre>
57  * <p>
58  * The store protocol, followed by the store ID, followed by the content Node Id
59  * the last element is used for mimetype calculation and browser default filename.
60  * <p>
61  * The 'attach' or 'direct' element is used to indicate whether to display the stream directly
62  * in the browser or download it as a file attachment.
63  * <p>
64  * By default, the download assumes that the content is on the
65  * {@link org.alfresco.model.ContentModel#PROP_CONTENT content property}.<br>
66  * To retrieve the content of a specific model property, use a 'property' arg, providing the workspace,
67  * node ID AND the qualified name of the property.
68  * <p>
69  * Like most Alfresco servlets, the URL may be followed by a valid 'ticket' argument for authentication:
70  * ?ticket=1234567890
71  * <p>
72  * And/or also followed by the "?guest=true" argument to force guest access login for the URL.
73  *
74  * @author Kevin Roast
75  */

76 public class DownloadContentServlet extends BaseServlet
77 {
78    private static final long serialVersionUID = -4558907921887235966L;
79    
80    private static Log logger = LogFactory.getLog(DownloadContentServlet.class);
81    
82    private static final String JavaDoc DOWNLOAD_URL = "/download/attach/{0}/{1}/{2}/{3}";
83    private static final String JavaDoc BROWSER_URL = "/download/direct/{0}/{1}/{2}/{3}";
84    
85    private static final String JavaDoc MIMETYPE_OCTET_STREAM = "application/octet-stream";
86    
87    private static final String JavaDoc MSG_ERROR_CONTENT_MISSING = "error_content_missing";
88    
89    private static final String JavaDoc ARG_PROPERTY = "property";
90    private static final String JavaDoc ARG_ATTACH = "attach";
91    
92    /**
93     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
94     */

95    protected void doGet(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res)
96       throws ServletException JavaDoc, IOException JavaDoc
97    {
98       // The URL contains multiple parts
99
// /alfresco/download/attach/workspace/SpacesStore/0000-0000-0000-0000/myfile.pdf
100
// the protocol, followed by the store, followed by the Id
101
// the last part is only used for mimetype and browser use
102
// may be followed by valid ticket for pre-authenticated usage: ?ticket=1234567890
103
String JavaDoc uri = req.getRequestURI();
104       
105       if (logger.isDebugEnabled())
106          logger.debug("Processing URL: " + uri + (req.getQueryString() != null ? ("?" + req.getQueryString()) : ""));
107       
108       AuthenticationStatus status = servletAuthenticate(req, res);
109       if (status == AuthenticationStatus.Failure)
110       {
111          return;
112       }
113       
114       // TODO: add compression here?
115
// see http://servlets.com/jservlet2/examples/ch06/ViewResourceCompress.java for example
116
// only really needed if we don't use the built in compression of the servlet container
117
StringTokenizer JavaDoc t = new StringTokenizer JavaDoc(uri, "/");
118       if (t.countTokens() < 7)
119       {
120          throw new IllegalArgumentException JavaDoc("Download URL did not contain all required args: " + uri);
121       }
122       
123       t.nextToken(); // skip web app name
124
t.nextToken(); // skip servlet name
125

126       String JavaDoc attachToken = t.nextToken();
127       boolean attachment = attachToken.equals(ARG_ATTACH);
128       
129       StoreRef storeRef = new StoreRef(t.nextToken(), t.nextToken());
130       String JavaDoc id = t.nextToken();
131       String JavaDoc filename = t.nextToken();
132       
133       // get property qualified name
134
QName propertyQName = null;
135       String JavaDoc property = req.getParameter(ARG_PROPERTY);
136       if (property == null || property.length() == 0)
137       {
138           propertyQName = ContentModel.PROP_CONTENT;
139       }
140       else
141       {
142           propertyQName = QName.createQName(property);
143       }
144       
145       // build noderef from the appropriate URL elements
146
NodeRef nodeRef = new NodeRef(storeRef, id);
147       
148       if (logger.isDebugEnabled())
149       {
150          logger.debug("Found NodeRef: " + nodeRef.toString());
151          logger.debug("Will use filename: " + filename);
152          logger.debug("For property: " + propertyQName);
153          logger.debug("With attachment mode: " + attachment);
154       }
155       
156       // get the services we need to retrieve the content
157
ServiceRegistry serviceRegistry = getServiceRegistry(getServletContext());
158       ContentService contentService = serviceRegistry.getContentService();
159       PermissionService permissionService = serviceRegistry.getPermissionService();
160       
161       try
162       {
163          // check that the user has at least READ_CONTENT access - else redirect to the login page
164
if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED)
165          {
166             if (logger.isDebugEnabled())
167                logger.debug("User does not have permissions to read content for NodeRef: " + nodeRef.toString());
168             redirectToLoginPage(req, res, getServletContext());
169             return;
170          }
171          
172          if (attachment == true)
173          {
174             // set header based on filename - will force a Save As from the browse if it doesn't recognise it
175
// this is better than the default response of the browser trying to display the contents
176
String JavaDoc encname = filename.replace('%', '=');
177             res.setHeader("Content-Disposition", "attachment;filename=\"=?ISO-8859-1?Q?" + encname + "?=\"");
178          }
179          
180          // get the content reader
181
ContentReader reader = contentService.getReader(nodeRef, propertyQName);
182          // ensure that it is safe to use
183
reader = FileContentReader.getSafeContentReader(
184                     reader,
185                     Application.getMessage(req.getSession(), MSG_ERROR_CONTENT_MISSING),
186                     nodeRef, reader);
187          
188          String JavaDoc mimetype = reader.getMimetype();
189          // fall back if unable to resolve mimetype property
190
if (mimetype == null || mimetype.length() == 0)
191          {
192             MimetypeService mimetypeMap = serviceRegistry.getMimetypeService();
193             mimetype = MIMETYPE_OCTET_STREAM;
194             int extIndex = filename.lastIndexOf('.');
195             if (extIndex != -1)
196             {
197                String JavaDoc ext = filename.substring(extIndex + 1);
198                String JavaDoc mt = mimetypeMap.getMimetypesByExtension().get(ext);
199                if (mt != null)
200                {
201                   mimetype = mt;
202                }
203             }
204          }
205          res.setContentType(mimetype);
206          
207          // get the content and stream directly to the response output stream
208
// assuming the repo is capable of streaming in chunks, this should allow large files
209
// to be streamed directly to the browser response stream.
210
try
211          {
212             reader.getContent( res.getOutputStream() );
213          }
214          catch (SocketException JavaDoc e)
215          {
216             if (e.getMessage().contains("ClientAbortException"))
217             {
218                // the client cut the connection - our mission was accomplished apart from a little error message
219
logger.error("Client aborted stream read:\n node: " + nodeRef + "\n content: " + reader);
220             }
221             else
222             {
223                throw e;
224             }
225          }
226       }
227       catch (Throwable JavaDoc err)
228       {
229          throw new AlfrescoRuntimeException("Error during download content servlet processing: " + err.getMessage(), err);
230       }
231    }
232    
233    /**
234     * Helper to generate a URL to a content node for downloading content from the server.
235     * The content is supplied as an HTTP1.1 attachment to the response. This generally means
236     * a browser should prompt the user to save the content to specified location.
237     *
238     * @param ref NodeRef of the content node to generate URL for (cannot be null)
239     * @param name File name to return in the URL (cannot be null)
240     *
241     * @return URL to download the content from the specified node
242     */

243    public final static String JavaDoc generateDownloadURL(NodeRef ref, String JavaDoc name)
244    {
245       String JavaDoc url = null;
246       
247       try
248       {
249          url = MessageFormat.format(DOWNLOAD_URL, new Object JavaDoc[] {
250                   ref.getStoreRef().getProtocol(),
251                   ref.getStoreRef().getIdentifier(),
252                   ref.getId(),
253                   Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } );
254       }
255       catch (UnsupportedEncodingException JavaDoc uee)
256       {
257          throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee);
258       }
259       
260       return url;
261    }
262    
263    /**
264     * Helper to generate a URL to a content node for downloading content from the server.
265     * The content is supplied directly in the reponse. This generally means a browser will
266     * attempt to open the content directly if possible, else it will prompt to save the file.
267     *
268     * @param ref NodeRef of the content node to generate URL for (cannot be null)
269     * @param name File name to return in the URL (cannot be null)
270     *
271     * @return URL to download the content from the specified node
272     */

273    public final static String JavaDoc generateBrowserURL(NodeRef ref, String JavaDoc name)
274    {
275       String JavaDoc url = null;
276       
277       try
278       {
279          url = MessageFormat.format(BROWSER_URL, new Object JavaDoc[] {
280                   ref.getStoreRef().getProtocol(),
281                   ref.getStoreRef().getIdentifier(),
282                   ref.getId(),
283                   Utils.replace(URLEncoder.encode(name, "UTF-8"), "+", "%20") } );
284       }
285       catch (UnsupportedEncodingException JavaDoc uee)
286       {
287          throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + ref, uee);
288       }
289       
290       return url;
291    }
292 }
293
Popular Tags