2 * Copyright 2008, 2009 Electronic Business Systems Ltd.
4 * This file is part of GSS.
6 * GSS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GSS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GSS. If not, see <http://www.gnu.org/licenses/>.
19 package gr.ebs.gss.server.rest;
21 import static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22 import gr.ebs.gss.client.exceptions.DuplicateNameException;
23 import gr.ebs.gss.client.exceptions.GSSIOException;
24 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
25 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
26 import gr.ebs.gss.client.exceptions.QuotaExceededException;
27 import gr.ebs.gss.client.exceptions.RpcException;
28 import gr.ebs.gss.server.Login;
29 import gr.ebs.gss.server.domain.FileUploadStatus;
30 import gr.ebs.gss.server.domain.User;
31 import gr.ebs.gss.server.domain.dto.FileBodyDTO;
32 import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
33 import gr.ebs.gss.server.domain.dto.FolderDTO;
34 import gr.ebs.gss.server.domain.dto.GroupDTO;
35 import gr.ebs.gss.server.domain.dto.PermissionDTO;
36 import gr.ebs.gss.server.ejb.ExternalAPI;
37 import gr.ebs.gss.server.ejb.TransactionHelper;
38 import gr.ebs.gss.server.webdav.Range;
39 import gr.ebs.gss.server.webdav.RequestUtil;
41 import java.io.BufferedReader;
42 import java.io.ByteArrayInputStream;
43 import java.io.ByteArrayOutputStream;
45 import java.io.FileInputStream;
46 import java.io.FileNotFoundException;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.InputStreamReader;
50 import java.io.OutputStreamWriter;
51 import java.io.PrintWriter;
52 import java.io.UnsupportedEncodingException;
54 import java.net.URISyntaxException;
55 import java.net.URLDecoder;
56 import java.net.URLEncoder;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.Date;
61 import java.util.HashSet;
62 import java.util.Iterator;
63 import java.util.List;
65 import java.util.StringTokenizer;
66 import java.util.concurrent.Callable;
68 import javax.servlet.ServletContext;
69 import javax.servlet.ServletException;
70 import javax.servlet.ServletOutputStream;
71 import javax.servlet.http.Cookie;
72 import javax.servlet.http.HttpServletRequest;
73 import javax.servlet.http.HttpServletResponse;
75 import org.apache.commons.codec.binary.Base64;
76 import org.apache.commons.fileupload.FileItemIterator;
77 import org.apache.commons.fileupload.FileItemStream;
78 import org.apache.commons.fileupload.FileUploadException;
79 import org.apache.commons.fileupload.ProgressListener;
80 import org.apache.commons.fileupload.servlet.ServletFileUpload;
81 import org.apache.commons.fileupload.util.Streams;
82 import org.apache.commons.httpclient.util.DateParseException;
83 import org.apache.commons.httpclient.util.DateUtil;
84 import org.apache.commons.logging.Log;
85 import org.apache.commons.logging.LogFactory;
86 import org.json.JSONArray;
87 import org.json.JSONException;
88 import org.json.JSONObject;
92 * A class that handles operations on the 'files' namespace.
96 public class FilesHandler extends RequestHandler {
98 * The request parameter name for fetching a different version.
100 private static final String VERSION_PARAM = "version";
103 * The request attribute containing the owner of the destination URI
104 * in a copy or move request.
106 private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
108 private static final int TRACK_PROGRESS_PERCENT = 5;
111 * The form parameter name that contains the signature in a browser POST upload.
113 private static final String AUTHORIZATION_PARAMETER = "Authorization";
116 * The form parameter name that contains the date in a browser POST upload.
118 private static final String DATE_PARAMETER = "Date";
121 * The request parameter name for making an upload progress request.
123 private static final String PROGRESS_PARAMETER = "progress";
126 * The request parameter name for restoring a previous version of a file.
128 private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
133 private static Log logger = LogFactory.getLog(FilesHandler.class);
136 * The servlet context provided by the call site.
138 private ServletContext context;
141 * The style sheet for displaying the directory listings.
143 private static final String GSS_CSS = "H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} " + "H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} " + "H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} " + "BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} " + "B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} " + "P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}" + "A {color : black;}" + "A.name {color : black;}" + "HR {color : #525D76;}";
147 * @param servletContext
149 public FilesHandler(ServletContext servletContext) {
150 context = servletContext;
153 private void updateAccounting(final User user, final Date date, final long bandwidthDiff) {
155 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
157 public Void call() throws Exception {
158 getService().updateAccounting(user, date, bandwidthDiff);
162 } catch (RuntimeException e) {
164 } catch (Exception e) {
165 // updateAccounting() doesn't throw any checked exceptions
171 * Serve the specified resource, optionally including the data content.
173 * @param req The servlet request we are processing
174 * @param resp The servlet response we are creating
175 * @param content Should the content be included?
177 * @exception IOException if an input/output error occurs
178 * @exception ServletException if a servlet-specified error occurs
179 * @throws RpcException
180 * @throws InsufficientPermissionsException
181 * @throws ObjectNotFoundException
184 protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
185 throws IOException, ServletException {
186 boolean authDeferred = getAuthDeferred(req);
187 String path = getInnerPath(req, PATH_FILES);
188 // if (!isValidResourceName(name)) {
189 // resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
195 path = URLDecoder.decode(path, "UTF-8");
196 } catch (IllegalArgumentException e) {
197 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
200 String progress = req.getParameter(PROGRESS_PARAMETER);
202 if (logger.isDebugEnabled())
204 logger.debug("Serving resource '" + path + "' headers and data");
206 logger.debug("Serving resource '" + path + "' headers only");
208 User user = getUser(req);
209 User owner = getOwner(req);
210 boolean exists = true;
211 Object resource = null;
212 FileHeaderDTO file = null;
213 FolderDTO folder = null;
215 resource = getService().getResourceAtPath(owner.getId(), path, false);
216 } catch (ObjectNotFoundException e) {
218 } catch (RpcException e) {
219 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
223 if (!exists && authDeferred) {
224 // We do not want to leak information if the request
225 // was not authenticated.
226 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
230 if (resource instanceof FolderDTO)
231 folder = (FolderDTO) resource;
233 file = (FileHeaderDTO) resource; // Note that file will be null, if (!exists).
235 // Now it's time to perform the deferred authentication check.
236 // Since regular signature checking was already performed,
237 // we need to check the read-all flag or the signature-in-parameters.
239 if (file != null && !file.isReadForAll() && content) {
240 // Check for GET with the signature in the request parameters.
241 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
242 String dateParam = req.getParameter(DATE_PARAMETER);
243 if (auth == null || dateParam == null) {
244 // Check for a valid authentication cookie.
245 if (req.getCookies() != null) {
246 boolean found = false;
247 for (Cookie cookie : req.getCookies())
248 if (Login.AUTH_COOKIE.equals(cookie.getName())) {
249 String cookieauth = cookie.getValue();
250 int sepIndex = cookieauth.indexOf(Login.COOKIE_SEPARATOR);
251 if (sepIndex == -1) {
252 handleAuthFailure(req, resp);
255 String username = URLDecoder.decode(cookieauth.substring(0, sepIndex), "US-ASCII");
258 user = getService().findUser(username);
259 } catch (RpcException e) {
260 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
264 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
267 req.setAttribute(USER_ATTRIBUTE, user);
268 String token = cookieauth.substring(sepIndex + 1);
269 if (user.getAuthToken() == null) {
270 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
273 if (!Arrays.equals(user.getAuthToken(), Base64.decodeBase64(token))) {
274 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
281 handleAuthFailure(req, resp);
285 handleAuthFailure(req, resp);
291 timestamp = DateUtil.parseDate(dateParam).getTime();
292 } catch (DateParseException e) {
293 resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
296 if (!isTimeValid(timestamp)) {
297 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
301 // Fetch the Authorization parameter and find the user specified in it.
302 String[] authParts = auth.split(" ");
303 if (authParts.length != 2) {
304 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
307 String username = authParts[0];
308 String signature = authParts[1];
311 user = getService().findUser(username);
312 } catch (RpcException e) {
313 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
317 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
320 req.setAttribute(USER_ATTRIBUTE, user);
322 // Remove the servlet path from the request URI.
323 String p = req.getRequestURI();
324 String servletPath = req.getContextPath() + req.getServletPath();
325 p = p.substring(servletPath.length());
326 // Validate the signature in the Authorization parameter.
327 String data = req.getMethod() + dateParam + p;
328 if (!isSignatureValid(signature, user, data)) {
329 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
334 else if(folder != null && folder.isReadForAll() || file != null && file.isReadForAll()){
335 //This case refers to a folder or file with public privileges
336 //For a read-for-all folder request, pretend the owner is making it.
338 req.setAttribute(USER_ATTRIBUTE, user);
339 }else if(folder != null && !folder.isReadForAll()){
340 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
344 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
348 // If the resource is not a collection, and the resource path
349 // ends with "/" or "\", return NOT FOUND.
351 if (path.endsWith("/") || path.endsWith("\\")) {
352 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
356 // Workaround for IE's broken caching behavior.
358 resp.setHeader("Expires", "-1");
360 // A request for upload progress.
361 if (progress != null && content) {
363 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
366 serveProgress(req, resp, progress, user, file);
370 // Fetch the version to retrieve, if specified.
371 String verStr = req.getParameter(VERSION_PARAM);
373 FileBodyDTO oldBody = null;
374 if (verStr != null && file != null)
376 version = Integer.valueOf(verStr);
377 } catch (NumberFormatException e) {
378 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
383 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
384 } catch (RpcException e) {
385 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
387 } catch (ObjectNotFoundException e) {
388 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
390 } catch (InsufficientPermissionsException e) {
391 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
395 // Check if the conditions specified in the optional If headers are
396 // satisfied. Doing this for folders would require recursive checking
397 // for all of their children, which in turn would defy the purpose of
400 // Checking If headers.
401 if (!checkIfHeaders(req, resp, file, oldBody))
404 else if(!checkIfModifiedSince(req, resp, folder))
407 // Find content type.
408 String contentType = null;
409 boolean isContentHtml = false;
410 boolean expectJSON = false;
413 contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
414 if (contentType == null) {
415 contentType = context.getMimeType(file.getName());
416 file.setMimeType(contentType);
418 } else { // folder != null
419 String accept = req.getHeader("Accept");
420 // The order in this conditional pessimizes the common API case,
421 // but is important for backwards compatibility with existing
422 // clients who send no accept header and expect a JSON response.
423 if (accept != null && accept.contains("text/html")) {
424 contentType = "text/html;charset=UTF-8";
425 isContentHtml = true;
426 //this is the case when clients send the appropriate headers, the contentType is "text/html"
427 //and expect a JSON response. The above check applies to FireGSS client
428 expectJSON = !authDeferred ? true : false;
431 contentType = "application/json;charset=UTF-8";
437 ArrayList ranges = null;
438 long contentLength = -1L;
441 // Parse range specifier.
442 ranges = parseRange(req, resp, file, oldBody);
444 resp.setHeader("ETag", getETag(file, oldBody));
445 // Last-Modified header.
446 String lastModified = oldBody == null ?
447 getLastModifiedHttp(file.getAuditInfo()) :
448 getLastModifiedHttp(oldBody.getAuditInfo());
449 resp.setHeader("Last-Modified", lastModified);
450 // X-GSS-Metadata header.
452 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
453 } catch (InsufficientPermissionsException e) {
454 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
457 // Get content length.
458 contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
459 // Special case for zero length files, which would cause a
460 // (silent) ISE when setting the output buffer size.
461 if (contentLength == 0L)
464 // Set the folder X-GSS-Metadata header.
466 resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
467 } catch (InsufficientPermissionsException e) {
468 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
472 ServletOutputStream ostream = null;
473 PrintWriter writer = null;
477 ostream = resp.getOutputStream();
478 } catch (IllegalStateException e) {
479 // If it fails, we try to get a Writer instead if we're
480 // trying to serve a text file
481 if ( contentType == null
482 || contentType.startsWith("text")
483 || contentType.endsWith("xml") )
484 writer = resp.getWriter();
488 if (folder != null || (ranges == null || ranges.isEmpty()) && req.getHeader("Range") == null || ranges == FULL) {
489 // Set the appropriate output headers
490 if (contentType != null) {
491 if (logger.isDebugEnabled())
492 logger.debug("contentType='" + contentType + "'");
493 resp.setContentType(contentType);
495 if (file != null && contentLength >= 0) {
496 if (logger.isDebugEnabled())
497 logger.debug("contentLength=" + contentLength);
498 if (contentLength < Integer.MAX_VALUE)
499 resp.setContentLength((int) contentLength);
502 // Set the content-length as String to be able to use a long
503 resp.setHeader("content-length", "" + contentLength);
506 InputStream renderResult = null;
507 String relativePath = getRelativePath(req);
508 String contextPath = req.getContextPath();
509 String servletPath = req.getServletPath();
510 String contextServletPath = contextPath + servletPath;
511 if (folder != null && content)
512 // Serve the directory browser for a public folder
513 if (isContentHtml && !expectJSON)
514 renderResult = renderHtml(contextServletPath, relativePath, folder,user);
515 // Serve the directory for an ordinary folder or for fireGSS client
518 renderResult = renderJson(user, folder);
519 } catch (InsufficientPermissionsException e) {
520 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
525 // Copy the input stream to our output stream (if requested)
528 resp.setBufferSize(output);
529 } catch (IllegalStateException e) {
534 if (needsContentDisposition(req))
535 resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
537 resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
539 copy(file, renderResult, ostream, req, oldBody);
541 copy(file, renderResult, writer, req, oldBody);
542 if (file!=null) updateAccounting(owner, new Date(), contentLength);
543 } catch (ObjectNotFoundException e) {
544 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
546 } catch (InsufficientPermissionsException e) {
547 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
549 } catch (RpcException e) {
550 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
555 if (ranges == null || ranges.isEmpty())
557 // Partial content response.
558 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
560 if (ranges.size() == 1) {
561 Range range = (Range) ranges.get(0);
562 resp.addHeader("Content-Range", "bytes "
564 + "-" + range.end + "/"
566 long length = range.end - range.start + 1;
567 if (length < Integer.MAX_VALUE)
568 resp.setContentLength((int) length);
570 // Set the content-length as String to be able to use a long
571 resp.setHeader("content-length", "" + length);
573 if (contentType != null) {
574 if (logger.isDebugEnabled())
575 logger.debug("contentType='" + contentType + "'");
576 resp.setContentType(contentType);
581 resp.setBufferSize(output);
582 } catch (IllegalStateException e) {
587 copy(file, ostream, range, req, oldBody);
589 copy(file, writer, range, req, oldBody);
590 updateAccounting(owner, new Date(), contentLength);
591 } catch (ObjectNotFoundException e) {
592 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
594 } catch (InsufficientPermissionsException e) {
595 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
597 } catch (RpcException e) {
598 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
603 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
606 resp.setBufferSize(output);
607 } catch (IllegalStateException e) {
612 copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
614 copy(file, writer, ranges.iterator(), contentType, req, oldBody);
615 updateAccounting(owner, new Date(), contentLength);
616 } catch (ObjectNotFoundException e) {
617 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
619 } catch (InsufficientPermissionsException e) {
620 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
622 } catch (RpcException e) {
623 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
632 * Handles an authentication failure. If no Authorization or Date request
633 * parameters and no Authorization, Date or X-GSS-Date headers were present,
634 * this is a browser request, so redirect to login and then let the user get
635 * back to the file. Otherwise it's a bogus client request and Forbidden is
638 private void handleAuthFailure(HttpServletRequest req, HttpServletResponse resp) throws IOException {
639 if (req.getParameter(AUTHORIZATION_PARAMETER) == null &&
640 req.getParameter(DATE_PARAMETER) == null &&
641 req.getHeader(AUTHORIZATION_HEADER) == null &&
642 req.getDateHeader(DATE_HEADER) == -1 &&
643 req.getDateHeader(GSS_DATE_HEADER) == -1)
644 resp.sendRedirect(getConfiguration().getString("loginUrl") +
645 "?next=" + req.getRequestURL().toString());
647 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
651 * Return the filename of the specified file properly formatted for
652 * including in the Content-Disposition header.
654 private String getDispositionFilename(FileHeaderDTO file) throws UnsupportedEncodingException {
655 return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
659 * Determines whether the user agent needs the Content-Disposition
660 * header to be set, in order to properly download a file.
662 * @param req the HTTP request
663 * @return true if the Content-Disposition HTTP header must be set
665 private boolean needsContentDisposition(HttpServletRequest req) {
666 /*String agent = req.getHeader("user-agent");
667 if (agent != null && agent.contains("MSIE"))
669 String dl = req.getParameter("dl");
676 * Sends a progress update on the amount of bytes received until now for
677 * a file that the current user is currently uploading.
679 * @param req the HTTP request
680 * @param resp the HTTP response
681 * @param parameter the value for the progress request parameter
682 * @param user the current user
683 * @param file the file being uploaded, or null if the request is about a new file
684 * @throws IOException if an I/O error occurs
686 private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
687 String parameter, User user, FileHeaderDTO file) throws IOException {
688 String filename = file == null ? parameter : file.getName();
690 FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
691 if (status == null) {
692 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
695 JSONObject json = new JSONObject();
696 json.put("bytesUploaded", status.getBytesUploaded()).
697 put("bytesTotal", status.getFileSize());
698 sendJson(req, resp, json.toString());
700 // Workaround for IE's broken caching behavior.
701 resp.setHeader("Expires", "-1");
703 } catch (RpcException e) {
704 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
706 } catch (JSONException e) {
707 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
713 * Server a POST request to create/modify a file or folder.
715 * @param req the HTTP request
716 * @param resp the HTTP response
717 * @exception IOException if an input/output error occurs
719 void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
720 boolean authDeferred = getAuthDeferred(req);
721 if (!authDeferred && req.getParameterMap().size() > 1) {
722 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
725 String path = getInnerPath(req, PATH_FILES);
726 path = path.endsWith("/")? path: path + '/';
728 path = URLDecoder.decode(path, "UTF-8");
729 } catch (IllegalArgumentException e) {
730 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
733 // We only defer authenticating multipart POST requests.
735 if (!ServletFileUpload.isMultipartContent(req)) {
736 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
739 handleMultipart(req, resp, path);
743 String newName = req.getParameter(NEW_FOLDER_PARAMETER);
745 boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
746 boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
747 boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
748 String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
749 String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
750 String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
752 if (newName != null){
753 if (!isValidResourceName(newName)) {
754 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
757 createFolder(req, resp, path, newName);
759 else if (hasUpdateParam)
760 updateResource(req, resp, path);
761 else if (hasTrashParam)
762 trashResource(req, resp, path);
763 else if (hasRestoreParam)
764 restoreResource(req, resp, path);
765 else if (copyTo != null)
766 copyResource(req, resp, path, copyTo);
767 else if (moveTo != null)
768 moveResource(req, resp, path, moveTo);
769 else if (restoreVersion != null)
770 restoreVersion(req, resp, path, restoreVersion);
772 // IE with Gears uses POST for multiple uploads.
773 putResource(req, resp);
777 * Restores a previous version for a file.
779 * @param req the HTTP request
780 * @param resp the HTTP response
781 * @param path the resource path
782 * @param version the version number to restore
783 * @throws IOException if an I/O error occurs
785 private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
786 final User user = getUser(req);
787 User owner = getOwner(req);
788 Object resource = null;
790 resource = getService().getResourceAtPath(owner.getId(), path, true);
791 } catch (ObjectNotFoundException e) {
792 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
794 } catch (RpcException e) {
795 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
798 if (resource instanceof FolderDTO) {
799 resp.sendError(HttpServletResponse.SC_CONFLICT);
804 final FileHeaderDTO file = (FileHeaderDTO) resource;
805 final int oldVersion = Integer.parseInt(version);
807 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
809 public Void call() throws Exception {
810 getService().restoreVersion(user.getId(), file.getId(), oldVersion);
814 } catch (InsufficientPermissionsException e) {
815 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
816 } catch (ObjectNotFoundException e) {
817 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
818 } catch (RpcException e) {
819 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
820 } catch (GSSIOException e) {
821 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
822 } catch (QuotaExceededException e) {
823 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
824 } catch (NumberFormatException e) {
825 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
826 } catch (Exception e) {
827 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
832 * A method for handling multipart POST requests for uploading
833 * files from browser-based JavaScript clients.
835 * @param request the HTTP request
836 * @param response the HTTP response
837 * @param path the resource path
838 * @throws IOException in case an error occurs writing to the
841 private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
842 if (logger.isDebugEnabled())
843 logger.debug("Multipart POST for resource: " + path);
845 User owner = getOwner(request);
846 boolean exists = true;
847 Object resource = null;
848 FileHeaderDTO file = null;
850 resource = getService().getResourceAtPath(owner.getId(), path, false);
851 } catch (ObjectNotFoundException e) {
853 } catch (RpcException e) {
854 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
859 if (resource instanceof FileHeaderDTO) {
860 file = (FileHeaderDTO) resource;
861 if (file.isDeleted()) {
862 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
866 response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
871 String parentPath = null;
873 parentPath = getParentPath(path);
874 parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
875 } catch (ObjectNotFoundException e) {
876 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
878 } catch (RpcException e) {
879 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
882 if (!(parent instanceof FolderDTO)) {
883 response.sendError(HttpServletResponse.SC_CONFLICT);
886 final FolderDTO folder = (FolderDTO) parent;
887 final String fileName = getLastElement(path);
889 if (!isValidResourceName(fileName)) {
890 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
894 FileItemIterator iter;
895 File uploadedFile = null;
897 // Create a new file upload handler.
898 ServletFileUpload upload = new ServletFileUpload();
899 StatusProgressListener progressListener = new StatusProgressListener(getService());
900 upload.setProgressListener(progressListener);
901 iter = upload.getItemIterator(request);
902 String dateParam = null;
904 while (iter.hasNext()) {
905 FileItemStream item = iter.next();
906 String name = item.getFieldName();
907 InputStream stream = item.openStream();
908 if (item.isFormField()) {
909 final String value = Streams.asString(stream);
910 if (name.equals(DATE_PARAMETER))
912 else if (name.equals(AUTHORIZATION_PARAMETER))
915 if (logger.isDebugEnabled())
916 logger.debug(name + ":" + value);
918 // Fetch the timestamp used to guard against replay attacks.
919 if (dateParam == null) {
920 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
926 timestamp = DateUtil.parseDate(dateParam).getTime();
927 } catch (DateParseException e) {
928 response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
931 if (!isTimeValid(timestamp)) {
932 response.sendError(HttpServletResponse.SC_FORBIDDEN);
936 // Fetch the Authorization parameter and find the user specified in it.
938 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
941 String[] authParts = auth.split(" ");
942 if (authParts.length != 2) {
943 response.sendError(HttpServletResponse.SC_FORBIDDEN);
946 String username = authParts[0];
947 String signature = authParts[1];
950 user = getService().findUser(username);
951 } catch (RpcException e) {
952 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
956 response.sendError(HttpServletResponse.SC_FORBIDDEN);
959 request.setAttribute(USER_ATTRIBUTE, user);
961 // Remove the servlet path from the request URI.
962 String p = request.getRequestURI();
963 String servletPath = request.getContextPath() + request.getServletPath();
964 p = p.substring(servletPath.length());
965 // Validate the signature in the Authorization parameter.
966 String data = request.getMethod() + dateParam + p;
967 if (!isSignatureValid(signature, user, data)) {
968 response.sendError(HttpServletResponse.SC_FORBIDDEN);
972 progressListener.setUserId(user.getId());
973 progressListener.setFilename(fileName);
974 final String contentType = item.getContentType();
977 uploadedFile = getService().uploadFile(stream, user.getId());
978 } catch (IOException ex) {
979 throw new GSSIOException(ex, false);
981 FileHeaderDTO fileDTO = null;
982 final File upf = uploadedFile;
983 final FileHeaderDTO f = file;
986 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
988 public FileHeaderDTO call() throws Exception {
989 return getService().createFile(u.getId(), folder.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
993 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
995 public FileHeaderDTO call() throws Exception {
996 return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
999 updateAccounting(owner, new Date(), fileDTO.getFileSize());
1000 getService().removeFileUploadProgress(user.getId(), fileName);
1003 // We can't return 204 here since GWT's onSubmitComplete won't fire.
1004 response.setContentType("text/html");
1005 response.getWriter().print("<pre></pre>");
1006 } catch (FileUploadException e) {
1007 String error = "Error while uploading file";
1008 logger.error(error, e);
1009 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1010 } catch (GSSIOException e) {
1011 if (uploadedFile != null && uploadedFile.exists())
1012 uploadedFile.delete();
1013 String error = "Error while uploading file";
1015 logger.error(error, e);
1017 logger.debug(error, e);
1018 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1019 } catch (DuplicateNameException e) {
1020 if (uploadedFile != null && uploadedFile.exists())
1021 uploadedFile.delete();
1022 String error = "The specified file name already exists in this folder";
1023 logger.error(error, e);
1024 response.sendError(HttpServletResponse.SC_CONFLICT, error);
1026 } catch (InsufficientPermissionsException e) {
1027 if (uploadedFile != null && uploadedFile.exists())
1028 uploadedFile.delete();
1029 String error = "You don't have the necessary permissions";
1030 logger.error(error, e);
1031 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
1033 } catch (QuotaExceededException e) {
1034 if (uploadedFile != null && uploadedFile.exists())
1035 uploadedFile.delete();
1036 String error = "Not enough free space available";
1037 if (logger.isDebugEnabled())
1038 logger.debug(error, e);
1039 response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
1041 } catch (ObjectNotFoundException e) {
1042 if (uploadedFile != null && uploadedFile.exists())
1043 uploadedFile.delete();
1044 String error = "A specified object was not found";
1045 logger.error(error, e);
1046 response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
1047 } catch (RpcException e) {
1048 if (uploadedFile != null && uploadedFile.exists())
1049 uploadedFile.delete();
1050 String error = "An error occurred while communicating with the service";
1051 logger.error(error, e);
1052 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1053 } catch (Exception e) {
1054 if (uploadedFile != null && uploadedFile.exists())
1055 uploadedFile.delete();
1056 String error = "An internal server error occurred";
1057 logger.error(error, e);
1058 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1063 * Move the resource in the specified path to the specified destination.
1065 * @param req the HTTP request
1066 * @param resp the HTTP response
1067 * @param path the path of the resource
1068 * @param moveTo the destination of the move procedure
1069 * @throws IOException if an input/output error occurs
1071 private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
1072 final User user = getUser(req);
1073 User owner = getOwner(req);
1074 Object resource = null;
1076 resource = getService().getResourceAtPath(owner.getId(), path, true);
1077 } catch (ObjectNotFoundException e) {
1078 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1080 } catch (RpcException e) {
1081 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1085 String destination = null;
1086 User destOwner = null;
1087 boolean exists = true;
1089 destination = getDestinationPath(req, encodePath(moveTo));
1090 destination = URLDecoder.decode(destination, "UTF-8");
1091 destOwner = getDestinationOwner(req);
1092 getService().getResourceAtPath(destOwner.getId(), destination, true);
1093 } catch (ObjectNotFoundException e) {
1095 } catch (URISyntaxException e) {
1096 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1098 } catch (RpcException e) {
1099 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1103 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1108 final User dOwner = destOwner;
1109 final String dest = destination;
1110 if (resource instanceof FolderDTO) {
1111 final FolderDTO folder = (FolderDTO) resource;
1112 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1114 public Void call() throws Exception {
1115 getService().moveFolderToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1120 final FileHeaderDTO file = (FileHeaderDTO) resource;
1121 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1123 public Void call() throws Exception {
1124 getService().moveFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1130 } catch (InsufficientPermissionsException e) {
1131 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1132 } catch (ObjectNotFoundException e) {
1133 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1134 } catch (RpcException e) {
1135 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1136 } catch (DuplicateNameException e) {
1137 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1138 } catch (GSSIOException e) {
1139 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1140 } catch (QuotaExceededException e) {
1141 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1142 } catch (Exception e) {
1143 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1148 * Copy the resource in the specified path to the specified destination.
1150 * @param req the HTTP request
1151 * @param resp the HTTP response
1152 * @param path the path of the resource
1153 * @param copyTo the destination of the copy procedure
1154 * @throws IOException if an input/output error occurs
1156 private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1157 final User user = getUser(req);
1158 User owner = getOwner(req);
1159 Object resource = null;
1161 resource = getService().getResourceAtPath(owner.getId(), path, true);
1162 } catch (ObjectNotFoundException e) {
1163 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1165 } catch (RpcException e) {
1166 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1170 String destination = null;
1171 User destOwner = null;
1172 boolean exists = true;
1174 String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1175 destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1176 destOwner = getDestinationOwner(req);
1177 getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1178 } catch (ObjectNotFoundException e) {
1180 } catch (URISyntaxException e) {
1181 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1183 } catch (RpcException e) {
1184 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1188 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1193 final User dOwner = destOwner;
1194 final String dest = destination;
1195 if (resource instanceof FolderDTO) {
1196 final FolderDTO folder = (FolderDTO) resource;
1197 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1199 public Void call() throws Exception {
1200 getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1205 final FileHeaderDTO file = (FileHeaderDTO) resource;
1206 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1208 public Void call() throws Exception {
1209 getService().copyFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1214 } catch (InsufficientPermissionsException e) {
1215 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1216 } catch (ObjectNotFoundException e) {
1217 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1218 } catch (RpcException e) {
1219 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1220 } catch (DuplicateNameException e) {
1221 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1222 } catch (GSSIOException e) {
1223 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1224 } catch (QuotaExceededException e) {
1225 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1226 } catch (Exception e) {
1227 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1231 private String encodePath(String path) throws UnsupportedEncodingException{
1232 StringTokenizer str = new StringTokenizer(path, "/:", true);
1233 String result = new String();
1234 while(str.hasMoreTokens()){
1235 String token = str.nextToken();
1236 if(!token.equals("/") && !token.equals(":"))
1237 token = URLEncoder.encode(token,"UTF-8");
1238 result = result + token;
1243 * A helper method that extracts the relative resource path,
1244 * after removing the 'files' namespace.
1245 * The path returned is <i>not</i> URL-decoded.
1247 * @param req the HTTP request
1248 * @param path the specified path
1249 * @return the path relative to the root folder
1250 * @throws URISyntaxException
1251 * @throws RpcException in case an error occurs while communicating
1253 * @throws UnsupportedEncodingException
1255 private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1256 URI uri = new URI(path);
1257 String dest = uri.getRawPath();
1258 // Remove the context path from the destination URI.
1259 String contextPath = req.getContextPath();
1260 if (!dest.startsWith(contextPath))
1261 throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1262 dest = dest.substring(contextPath.length());
1263 // Remove the servlet path from the destination URI.
1264 String servletPath = req.getServletPath();
1265 if (!dest.startsWith(servletPath))
1266 throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1267 dest = dest.substring(servletPath.length());
1268 // Strip the username part
1269 if (dest.length() < 2)
1270 throw new URISyntaxException(dest, "No username in the destination URI");
1271 int slash = dest.substring(1).indexOf('/');
1273 throw new URISyntaxException(dest, "No username in the destination URI");
1274 // Decode the user to get the proper characters (mainly the @)
1275 String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1277 o = getService().findUser(owner);
1279 throw new URISyntaxException(dest, "User " + owner + " not found");
1281 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1282 dest = dest.substring(slash + 1);
1284 // Chop the resource namespace part
1285 dest = dest.substring(RequestHandler.PATH_FILES.length());
1287 dest = dest.endsWith("/")? dest: dest + '/';
1292 * Move the resource in the specified path to the trash bin.
1294 * @param req the HTTP request
1295 * @param resp the HTTP response
1296 * @param path the path of the resource
1297 * @throws IOException if an input/output error occurs
1299 private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1300 final User user = getUser(req);
1301 User owner = getOwner(req);
1302 Object resource = null;
1304 resource = getService().getResourceAtPath(owner.getId(), path, true);
1305 } catch (ObjectNotFoundException e) {
1306 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1308 } catch (RpcException e) {
1309 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1314 if (resource instanceof FolderDTO) {
1315 final FolderDTO folder = (FolderDTO) resource;
1316 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1318 public Void call() throws Exception {
1319 getService().moveFolderToTrash(user.getId(), folder.getId());
1324 final FileHeaderDTO file = (FileHeaderDTO) resource;
1325 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1327 public Void call() throws Exception {
1328 getService().moveFileToTrash(user.getId(), file.getId());
1333 } catch (InsufficientPermissionsException e) {
1334 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1335 } catch (ObjectNotFoundException e) {
1336 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1337 } catch (RpcException e) {
1338 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1339 } catch (Exception e) {
1340 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1345 * Restore the resource in the specified path from the trash bin.
1347 * @param req the HTTP request
1348 * @param resp the HTTP response
1349 * @param path the path of the resource
1350 * @throws IOException if an input/output error occurs
1352 private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1353 final User user = getUser(req);
1354 User owner = getOwner(req);
1355 Object resource = null;
1357 resource = getService().getResourceAtPath(owner.getId(), path, false);
1358 } catch (ObjectNotFoundException e) {
1359 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1361 } catch (RpcException e) {
1362 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1367 if (resource instanceof FolderDTO) {
1368 final FolderDTO folder = (FolderDTO) resource;
1369 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1371 public Void call() throws Exception {
1372 getService().removeFolderFromTrash(user.getId(), folder.getId());
1377 final FileHeaderDTO file = (FileHeaderDTO) resource;
1378 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1380 public Void call() throws Exception {
1381 getService().removeFileFromTrash(user.getId(), file.getId());
1386 } catch (InsufficientPermissionsException e) {
1387 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1388 } catch (ObjectNotFoundException e) {
1389 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1390 } catch (RpcException e) {
1391 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1392 } catch (Exception e) {
1393 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1398 * Update the resource in the specified path.
1400 * @param req the HTTP request
1401 * @param resp the HTTP response
1402 * @param path the path of the resource
1403 * @throws IOException if an input/output error occurs
1405 private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1406 final User user = getUser(req);
1407 User owner = getOwner(req);
1408 Object resource = null;
1411 resource = getService().getResourceAtPath(owner.getId(), path, false);
1412 } catch (ObjectNotFoundException e) {
1413 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1415 } catch (RpcException e) {
1416 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1419 StringBuffer input = new StringBuffer();
1420 JSONObject json = null;
1421 if (req.getContentType() != null && req.getContentType().startsWith("application/x-www-form-urlencoded"))
1422 input.append(req.getParameter(RESOURCE_UPDATE_PARAMETER));
1424 // Assume application/json
1425 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1427 while ((line = reader.readLine()) != null)
1432 json = new JSONObject(input.toString());
1433 if (logger.isDebugEnabled())
1434 logger.debug("JSON update: " + json);
1435 if (resource instanceof FolderDTO) {
1436 final FolderDTO folder = (FolderDTO) resource;
1437 String name = json.optString("name");
1438 if (!isValidResourceName(name)) {
1439 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
1442 JSONArray permissions = json.optJSONArray("permissions");
1443 Set<PermissionDTO> perms = null;
1444 if (permissions != null)
1445 perms = parsePermissions(user, permissions);
1446 Boolean readForAll = null;
1447 if (json.opt("readForAll") != null)
1448 readForAll = json.optBoolean("readForAll");
1449 if (!name.isEmpty() || permissions != null || readForAll != null) {
1450 final String fName = name.isEmpty()? null: name;
1451 final Boolean freadForAll = readForAll;
1452 final Set<PermissionDTO> fPerms = perms;
1453 FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1455 public FolderDTO call() throws Exception {
1456 return getService().updateFolder(user.getId(), folder.getId(), fName, freadForAll, fPerms);
1460 resp.getWriter().println(getNewUrl(req, folderUpdated));
1463 final FileHeaderDTO file = (FileHeaderDTO) resource;
1465 if (json.opt("name") != null)
1466 name = json.optString("name");
1467 if (!isValidResourceName(name)) {
1468 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
1471 Long modificationDate = null;
1472 if (json.optLong("modificationDate") != 0)
1473 modificationDate = json.optLong("modificationDate");
1474 Boolean versioned = null;
1475 if (json.opt("versioned") != null)
1476 versioned = json.getBoolean("versioned");
1477 JSONArray tagset = json.optJSONArray("tags");
1479 StringBuffer t = new StringBuffer();
1480 if (tagset != null) {
1481 for (int i = 0; i < tagset.length(); i++)
1482 t.append(tagset.getString(i) + ',');
1483 tags = t.toString();
1485 JSONArray permissions = json.optJSONArray("permissions");
1486 Set<PermissionDTO> perms = null;
1487 if (permissions != null)
1488 perms = parsePermissions(user, permissions);
1489 Boolean readForAll = null;
1490 if (json.opt("readForAll") != null)
1491 readForAll = json.optBoolean("readForAll");
1492 if (name != null || tags != null || modificationDate != null
1493 || versioned != null || perms != null
1494 || readForAll != null) {
1495 final String fName = name;
1496 final String fTags = tags;
1497 final Date mDate = modificationDate != null? new Date(modificationDate): null;
1498 final Boolean fVersioned = versioned;
1499 final Boolean fReadForAll = readForAll;
1500 final Set<PermissionDTO> fPerms = perms;
1501 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1503 public Object call() throws Exception {
1504 getService().updateFile(user.getId(), file.getId(),
1505 fName, fTags, mDate, fVersioned,
1506 fReadForAll, fPerms);
1513 } catch (JSONException e) {
1514 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1515 } catch (InsufficientPermissionsException e) {
1516 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1517 } catch (ObjectNotFoundException e) {
1518 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1519 } catch (DuplicateNameException e) {
1520 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1521 } catch (RpcException e) {
1522 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1523 } catch (Exception e) {
1524 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1530 * Returns the new URL of an updated folder.
1532 private String getNewUrl(HttpServletRequest req, FolderDTO folder) throws UnsupportedEncodingException {
1533 String parentUrl = URLDecoder.decode(getContextPath(req, true),"UTF-8");
1534 String fpath = URLDecoder.decode(getRelativePath(req), "UTF-8");
1535 if (parentUrl.indexOf(fpath) != -1)
1536 parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1537 if(!parentUrl.endsWith("/"))
1538 parentUrl = parentUrl+"/";
1539 parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1544 * Helper method to convert a JSON array of permissions into a set of
1545 * PermissionDTO objects.
1547 * @param user the current user
1548 * @param permissions the JSON array to parse
1549 * @return the parsed set of permissions
1550 * @throws JSONException if there was an error parsing the JSON object
1551 * @throws RpcException if there was an error communicating with the EJB
1552 * @throws ObjectNotFoundException if the user could not be found
1553 * @throws UnsupportedEncodingException
1555 private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1556 throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1557 if (permissions == null)
1559 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1560 for (int i = 0; i < permissions.length(); i++) {
1561 JSONObject j = permissions.getJSONObject(i);
1562 PermissionDTO perm = new PermissionDTO();
1563 perm.setModifyACL(j.optBoolean("modifyACL"));
1564 perm.setRead(j.optBoolean("read"));
1565 perm.setWrite(j.optBoolean("write"));
1566 String permUser = j.optString("user");
1567 if (!permUser.isEmpty()) {
1568 User u = getService().findUser(permUser);
1570 throw new ObjectNotFoundException("User " + permUser + " not found");
1571 perm.setUser(u.getDTO());
1573 // 31/8/2009: Add optional groupUri which takes priority if it exists
1574 String permGroupUri = j.optString("groupUri");
1575 String permGroup = j.optString("group");
1576 if (!permGroupUri.isEmpty()) {
1577 String[] names = permGroupUri.split("/");
1578 String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1579 String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1580 User u = getService().findUser(usr);
1582 throw new ObjectNotFoundException("User " + permUser + " not found");
1583 GroupDTO g = getService().getGroup(u.getId(), grp);
1586 else if (!permGroup.isEmpty()) {
1587 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1590 if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1591 throw new JSONException("A permission must correspond to either a user or a group");
1598 * Creates a new folder with the specified name under the folder in the provided path.
1600 * @param req the HTTP request
1601 * @param resp the HTTP response
1602 * @param path the parent folder path
1603 * @param folderName the name of the new folder
1604 * @throws IOException if an input/output error occurs
1606 private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1607 if (logger.isDebugEnabled())
1608 logger.debug("Creating folder " + folderName + " in '" + path);
1610 final User user = getUser(req);
1611 User owner = getOwner(req);
1612 boolean exists = true;
1614 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1615 } catch (ObjectNotFoundException e) {
1617 } catch (RpcException e) {
1618 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1623 resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1624 ", " + METHOD_HEAD);
1625 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1631 parent = getService().getResourceAtPath(owner.getId(), path, true);
1632 } catch (ObjectNotFoundException e) {
1633 resp.sendError(HttpServletResponse.SC_CONFLICT);
1635 } catch (RpcException e) {
1636 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1640 if (parent instanceof FolderDTO) {
1641 final FolderDTO folder = (FolderDTO) parent;
1642 FolderDTO newFolder = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1644 public FolderDTO call() throws Exception {
1645 return getService().createFolder(user.getId(), folder.getId(), folderName);
1649 String newResource = getApiRoot() + newFolder.getURI();
1650 resp.setHeader("Location", newResource);
1651 resp.setContentType("text/plain");
1652 PrintWriter out = resp.getWriter();
1653 out.println(newResource);
1655 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1658 } catch (DuplicateNameException e) {
1659 resp.sendError(HttpServletResponse.SC_CONFLICT);
1661 } catch (InsufficientPermissionsException e) {
1662 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1664 } catch (ObjectNotFoundException e) {
1665 resp.sendError(HttpServletResponse.SC_CONFLICT);
1667 } catch (RpcException e) {
1668 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1670 } catch (Exception e) {
1671 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1674 resp.setStatus(HttpServletResponse.SC_CREATED);
1680 * @throws IOException
1681 * @throws FileNotFoundException
1683 void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1684 String path = getInnerPath(req, PATH_FILES);
1686 path = URLDecoder.decode(path, "UTF-8");
1687 } catch (IllegalArgumentException e) {
1688 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1691 if (logger.isDebugEnabled())
1692 logger.debug("Updating resource: " + path);
1694 final User user = getUser(req);
1695 User owner = getOwner(req);
1696 boolean exists = true;
1697 Object resource = null;
1698 FileHeaderDTO file = null;
1700 resource = getService().getResourceAtPath(owner.getId(), path, false);
1701 } catch (ObjectNotFoundException e) {
1703 } catch (RpcException e) {
1704 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1709 if (resource instanceof FileHeaderDTO)
1710 file = (FileHeaderDTO) resource;
1712 resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1715 boolean result = true;
1717 // Temporary content file used to support partial PUT.
1718 File contentFile = null;
1720 Range range = parseContentRange(req, resp);
1722 InputStream resourceInputStream = null;
1724 // Append data specified in ranges to existing content for this
1725 // resource - create a temporary file on the local filesystem to
1726 // perform this operation.
1727 // Assume just one range is specified for now
1728 if (range != null) {
1730 contentFile = executePartialPut(req, range, path);
1731 } catch (RpcException e) {
1732 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1734 } catch (ObjectNotFoundException e) {
1735 resp.sendError(HttpServletResponse.SC_CONFLICT);
1737 } catch (InsufficientPermissionsException e) {
1738 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1741 resourceInputStream = new FileInputStream(contentFile);
1743 resourceInputStream = req.getInputStream();
1746 FolderDTO folder = null;
1747 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1748 if (!(parent instanceof FolderDTO)) {
1749 resp.sendError(HttpServletResponse.SC_CONFLICT);
1752 folder = (FolderDTO) parent;
1753 final String name = getLastElement(path);
1754 final String mimeType = context.getMimeType(name);
1755 File uploadedFile = null;
1757 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1758 } catch (IOException ex) {
1759 throw new GSSIOException(ex, false);
1761 FileHeaderDTO fileDTO = null;
1762 final File uploadedf = uploadedFile;
1763 final FolderDTO parentf = folder;
1764 final FileHeaderDTO f = file;
1766 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1768 public FileHeaderDTO call() throws Exception {
1769 return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1773 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1775 public FileHeaderDTO call() throws Exception {
1776 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1780 updateAccounting(owner, new Date(), fileDTO.getFileSize());
1781 getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1782 } catch(ObjectNotFoundException e) {
1784 } catch (RpcException e) {
1785 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1787 } catch (IOException e) {
1788 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1790 } catch (GSSIOException e) {
1791 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1793 } catch (DuplicateNameException e) {
1794 resp.sendError(HttpServletResponse.SC_CONFLICT);
1796 } catch (InsufficientPermissionsException e) {
1797 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1799 } catch (QuotaExceededException e) {
1800 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1802 } catch (Exception e) {
1803 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1809 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1811 resp.setStatus(HttpServletResponse.SC_CREATED);
1813 resp.sendError(HttpServletResponse.SC_CONFLICT);
1817 * Delete a resource.
1819 * @param req The servlet request we are processing
1820 * @param resp The servlet response we are processing
1821 * @throws IOException if the response cannot be sent
1823 void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1824 String path = getInnerPath(req, PATH_FILES);
1825 if (logger.isDebugEnabled())
1826 logger.debug("Deleting resource '" + path);
1827 path = URLDecoder.decode(path, "UTF-8");
1828 final User user = getUser(req);
1829 User owner = getOwner(req);
1830 boolean exists = true;
1831 Object object = null;
1833 object = getService().getResourceAtPath(owner.getId(), path, false);
1834 } catch (ObjectNotFoundException e) {
1836 } catch (RpcException e) {
1837 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1842 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1846 FolderDTO folder = null;
1847 FileHeaderDTO file = null;
1848 if (object instanceof FolderDTO)
1849 folder = (FolderDTO) object;
1851 file = (FileHeaderDTO) object;
1855 final FileHeaderDTO f = file;
1856 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1858 public Void call() throws Exception {
1859 getService().deleteFile(user.getId(), f.getId());
1863 } catch (InsufficientPermissionsException e) {
1864 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1866 } catch (ObjectNotFoundException e) {
1867 // Although we had already found the object, it was
1868 // probably deleted from another thread.
1869 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1871 } catch (RpcException e) {
1872 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1874 } catch (Exception e) {
1875 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1878 else if (folder != null)
1880 final FolderDTO fo = folder;
1881 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1883 public Void call() throws Exception {
1884 getService().deleteFolder(user.getId(), fo.getId());
1888 } catch (InsufficientPermissionsException e) {
1889 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1891 } catch (ObjectNotFoundException e) {
1892 resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1894 } catch (RpcException e) {
1895 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1897 } catch (Exception e) {
1898 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1901 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1906 * Return an InputStream to a JSON representation of the contents
1907 * of this directory.
1909 * @param user the user that made the request
1910 * @param folder the specified directory
1911 * @return an input stream with the rendered contents
1912 * @throws IOException if the response cannot be sent
1913 * @throws ServletException
1914 * @throws InsufficientPermissionsException if the user does not have
1915 * the necessary privileges to read the directory
1917 private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1918 ServletException, InsufficientPermissionsException {
1919 JSONObject json = new JSONObject();
1921 json.put("name", folder.getName()).
1922 put("owner", folder.getOwner().getUsername()).
1923 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1924 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1925 put("deleted", folder.isDeleted()).
1926 put("readForAll", folder.isReadForAll());
1928 if (folder.getAuditInfo().getModifiedBy() != null)
1929 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1930 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1931 if (folder.getParent() != null) {
1932 JSONObject j = new JSONObject();
1933 j.put("uri", getApiRoot() + folder.getParent().getURI());
1934 j.put("name", folder.getParent().getName());
1935 json.put("parent", j);
1937 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1938 for (FolderDTO f: folder.getSubfolders())
1939 if (!f.isDeleted()) {
1940 JSONObject j = new JSONObject();
1941 j.put("name", f.getName()).
1942 put("uri", getApiRoot() + f.getURI());
1945 json.put("folders", subfolders);
1946 List<JSONObject> files = new ArrayList<JSONObject>();
1947 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1948 for (FileHeaderDTO f: fileHeaders) {
1949 JSONObject j = new JSONObject();
1950 j.put("name", f.getName()).
1951 put("owner", f.getOwner().getUsername()).
1952 put("deleted", f.isDeleted()).
1953 put("version", f.getVersion()).
1954 put("content", f.getMimeType()).
1955 put("size", f.getFileSize()).
1956 put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1957 put("path", f.getFolder().getPath()).
1958 put("uri", getApiRoot() + f.getURI());
1959 if (f.getAuditInfo().getModificationDate() != null)
1960 j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1963 json.put("files", files);
1964 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1965 json.put("permissions", renderJson(perms));
1966 } catch (JSONException e) {
1967 throw new ServletException(e);
1968 } catch (ObjectNotFoundException e) {
1969 throw new ServletException(e);
1970 } catch (RpcException e) {
1971 throw new ServletException(e);
1974 // Prepare a writer to a buffered area
1975 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1976 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1977 PrintWriter writer = new PrintWriter(osWriter);
1979 // Return an input stream to the underlying bytes
1980 writer.write(json.toString());
1982 return new ByteArrayInputStream(stream.toByteArray());
1986 * Return a String with a JSON representation of the metadata
1987 * of the specified folder.
1988 * @throws RpcException
1989 * @throws InsufficientPermissionsException
1990 * @throws ObjectNotFoundException
1992 private String renderJsonMetadata(User user, FolderDTO folder)
1993 throws ServletException, InsufficientPermissionsException {
1994 // Check if the user has read permission.
1996 if (!getService().canReadFolder(user.getId(), folder.getId()))
1997 throw new InsufficientPermissionsException();
1998 } catch (ObjectNotFoundException e) {
1999 throw new ServletException(e);
2000 } catch (RpcException e) {
2001 throw new ServletException(e);
2004 JSONObject json = new JSONObject();
2006 json.put("name", folder.getName()).
2007 put("owner", folder.getOwner().getUsername()).
2008 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
2009 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
2010 put("deleted", folder.isDeleted());
2011 if (folder.getAuditInfo().getModifiedBy() != null)
2012 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
2013 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
2014 } catch (JSONException e) {
2015 throw new ServletException(e);
2017 return json.toString();
2021 * Return a String with a JSON representation of the metadata
2022 * of the specified file. If an old file body is provided, then
2023 * the metadata of that particular version will be returned.
2025 * @param user the user that made the request
2026 * @param file the specified file header
2027 * @param oldBody the version number
2028 * @return the JSON-encoded file
2029 * @throws ServletException
2030 * @throws InsufficientPermissionsException if the user does not have
2031 * the necessary privileges to read the directory
2033 private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
2034 throws ServletException, InsufficientPermissionsException {
2035 JSONObject json = new JSONObject();
2037 // Need to encode file name in order to properly display it in the web client.
2038 json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
2039 put("owner", file.getOwner().getUsername()).
2040 put("versioned", file.isVersioned()).
2041 put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
2042 put("readForAll", file.isReadForAll()).
2043 put("tags", renderJson(file.getTags())).
2044 put("path", file.getFolder().getPath()).
2045 put("uri", getApiRoot() + file.getURI()).
2046 put("deleted", file.isDeleted());
2047 JSONObject j = new JSONObject();
2048 j.put("uri", getApiRoot() + file.getFolder().getURI()).
2049 put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
2050 json.put("folder", j);
2051 if (oldBody != null)
2052 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
2053 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
2054 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
2055 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
2056 put("content", oldBody.getMimeType()).
2057 put("size", oldBody.getFileSize());
2059 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
2060 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
2061 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
2062 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
2063 put("content", file.getMimeType()).
2064 put("size", file.getFileSize());
2065 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
2066 json.put("permissions", renderJson(perms));
2067 } catch (JSONException e) {
2068 throw new ServletException(e);
2069 } catch (ObjectNotFoundException e) {
2070 throw new ServletException(e);
2071 } catch (RpcException e) {
2072 throw new ServletException(e);
2073 } catch (UnsupportedEncodingException e) {
2074 throw new ServletException(e);
2077 return json.toString();
2081 * Return a String with a JSON representation of the
2082 * specified set of permissions.
2084 * @param permissions the set of permissions
2085 * @return the JSON-encoded object
2086 * @throws JSONException
2087 * @throws UnsupportedEncodingException
2089 private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
2090 JSONArray perms = new JSONArray();
2091 for (PermissionDTO p: permissions) {
2092 JSONObject permission = new JSONObject();
2093 permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2094 if (p.getUser() != null)
2095 permission.put("user", p.getUser().getUsername());
2096 if (p.getGroup() != null) {
2097 GroupDTO group = p.getGroup();
2098 permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2099 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2101 perms.put(permission);
2107 * Return a String with a JSON representation of the
2108 * specified collection of tags.
2110 * @param tags the collection of tags
2111 * @return the JSON-encoded object
2112 * @throws JSONException
2113 * @throws UnsupportedEncodingException
2115 private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2116 JSONArray tagArray = new JSONArray();
2117 for (String t: tags)
2118 tagArray.put(URLEncoder.encode(t,"UTF-8"));
2123 * Retrieves the user who owns the destination namespace, for a
2124 * copy or move request.
2126 * @param req the HTTP request
2127 * @return the owner of the namespace
2129 protected User getDestinationOwner(HttpServletRequest req) {
2130 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2134 * A helper inner class for updating the progress status of a file upload.
2138 public static class StatusProgressListener implements ProgressListener {
2139 private int percentLogged = 0;
2140 private long bytesTransferred = 0;
2142 private long fileSize = -100;
2144 private Long userId;
2146 private String filename;
2148 private ExternalAPI service;
2150 public StatusProgressListener(ExternalAPI aService) {
2155 * Modify the userId.
2157 * @param aUserId the userId to set
2159 public void setUserId(Long aUserId) {
2164 * Modify the filename.
2166 * @param aFilename the filename to set
2168 public void setFilename(String aFilename) {
2169 filename = aFilename;
2173 public void update(long bytesRead, long contentLength, int items) {
2174 //monitoring per percent of bytes uploaded
2175 bytesTransferred = bytesRead;
2176 if (fileSize != contentLength)
2177 fileSize = contentLength;
2178 int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2180 if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2181 if (percent != percentLogged){
2182 percentLogged = percent;
2184 if (userId != null && filename != null)
2185 service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2186 } catch (ObjectNotFoundException e) {
2187 // Swallow the exception since it is going to be caught
2188 // by previously called methods
2195 * Return an InputStream to an HTML representation of the contents of this
2198 * @param contextPath Context path to which our internal paths are relative
2199 * @param path the requested path to the resource
2200 * @param folder the specified directory
2201 * @param user the specified user
2202 * @return an input stream with the rendered contents
2203 * @throws IOException
2204 * @throws ServletException
2206 private InputStream renderHtml(String contextPath, String path, FolderDTO folder, User user)
2207 throws IOException, ServletException {
2208 String name = folder.getName();
2209 // Prepare a writer to a buffered area
2210 ByteArrayOutputStream stream = new ByteArrayOutputStream();
2211 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
2212 PrintWriter writer = new PrintWriter(osWriter);
2213 StringBuffer sb = new StringBuffer();
2214 // rewriteUrl(contextPath) is expensive. cache result for later reuse
2215 String rewrittenContextPath = rewriteUrl(contextPath);
2216 // Render the page header
2217 sb.append("<html>\r\n");
2218 sb.append("<head>\r\n");
2219 sb.append("<title>");
2220 sb.append("Index of " + name);
2221 sb.append("</title>\r\n");
2222 sb.append("<STYLE><!--");
2224 sb.append("--></STYLE> ");
2225 sb.append("</head>\r\n");
2226 sb.append("<body>");
2228 sb.append("Index of " + name);
2230 // Render the link to our parent (if required)
2231 String parentDirectory = path;
2232 if (parentDirectory.endsWith("/"))
2233 parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
2234 int slash = parentDirectory.lastIndexOf('/');
2236 String parent = path.substring(0, slash);
2237 sb.append(" - <a href=\"");
2238 sb.append(rewrittenContextPath);
2239 if (parent.equals(""))
2242 if (!parent.endsWith("/"))
2246 sb.append("Up To " + parent);
2252 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2254 sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n");
2256 // Render the column headings
2257 sb.append("<tr>\r\n");
2258 sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
2260 sb.append("</strong></font></td>\r\n");
2261 sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
2263 sb.append("</strong></font></td>\r\n");
2264 sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
2265 sb.append("Last modified");
2266 sb.append("</strong></font></td>\r\n");
2268 // Render the directory entries within this directory
2269 boolean shade = false;
2270 Iterator iter = folder.getSubfolders().iterator();
2271 while (iter.hasNext()) {
2272 FolderDTO subf = (FolderDTO) iter.next();
2273 if(subf.isReadForAll() && !subf.isDeleted()){
2274 String resourceName = subf.getName();
2275 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2280 sb.append(" bgcolor=\"#eeeeee\"");
2284 sb.append("<td align=\"left\"> \r\n");
2285 sb.append("<a href=\"");
2286 sb.append(rewrittenContextPath+path);
2287 sb.append(rewriteUrl(resourceName));
2289 sb.append("\"><tt>");
2290 sb.append(RequestUtil.filter(resourceName));
2292 sb.append("</tt></a></td>\r\n");
2294 sb.append("<td align=\"right\"><tt>");
2295 sb.append(" ");
2296 sb.append("</tt></td>\r\n");
2298 sb.append("<td align=\"right\"><tt>");
2299 sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2300 sb.append("</tt></td>\r\n");
2302 sb.append("</tr>\r\n");
2306 List<FileHeaderDTO> files;
2308 files = getService().getFiles(user.getId(), folder.getId(), true);
2309 } catch (ObjectNotFoundException e) {
2310 throw new ServletException(e.getMessage());
2311 } catch (InsufficientPermissionsException e) {
2312 throw new ServletException(e.getMessage());
2313 } catch (RpcException e) {
2314 throw new ServletException(e.getMessage());
2316 for (FileHeaderDTO file : files)
2317 //Display only file resources that are marked as public and are not deleted
2318 if(file.isReadForAll() && !file.isDeleted()){
2319 String resourceName = file.getName();
2320 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2325 sb.append(" bgcolor=\"#eeeeee\"");
2329 sb.append("<td align=\"left\"> \r\n");
2330 sb.append("<a href=\"");
2331 sb.append(rewrittenContextPath + path);
2332 sb.append(rewriteUrl(resourceName));
2333 sb.append("\"><tt>");
2334 sb.append(RequestUtil.filter(resourceName));
2335 sb.append("</tt></a></td>\r\n");
2337 sb.append("<td align=\"right\"><tt>");
2338 sb.append(renderSize(file.getFileSize()));
2339 sb.append("</tt></td>\r\n");
2341 sb.append("<td align=\"right\"><tt>");
2342 sb.append(getLastModifiedHttp(file.getAuditInfo()));
2343 sb.append("</tt></td>\r\n");
2345 sb.append("</tr>\r\n");
2348 // Render the page footer
2349 sb.append("</table>\r\n");
2351 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2352 sb.append("</body>\r\n");
2353 sb.append("</html>\r\n");
2355 // Return an input stream to the underlying bytes
2356 writer.write(sb.toString());
2358 return new ByteArrayInputStream(stream.toByteArray());