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 gr.ebs.gss.client.exceptions.DuplicateNameException;
22 import gr.ebs.gss.client.exceptions.GSSIOException;
23 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
24 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
25 import gr.ebs.gss.client.exceptions.QuotaExceededException;
26 import gr.ebs.gss.client.exceptions.RpcException;
27 import gr.ebs.gss.server.domain.FileUploadStatus;
28 import gr.ebs.gss.server.domain.User;
29 import gr.ebs.gss.server.domain.dto.FileBodyDTO;
30 import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
31 import gr.ebs.gss.server.domain.dto.FolderDTO;
32 import gr.ebs.gss.server.domain.dto.GroupDTO;
33 import gr.ebs.gss.server.domain.dto.PermissionDTO;
34 import gr.ebs.gss.server.ejb.ExternalAPI;
35 import gr.ebs.gss.server.webdav.Range;
37 import java.io.BufferedReader;
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
41 import java.io.FileInputStream;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.InputStreamReader;
46 import java.io.OutputStreamWriter;
47 import java.io.PrintWriter;
48 import java.io.UnsupportedEncodingException;
50 import java.net.URISyntaxException;
51 import java.net.URLDecoder;
52 import java.net.URLEncoder;
53 import java.util.ArrayList;
54 import java.util.Collection;
55 import java.util.Date;
56 import java.util.HashSet;
57 import java.util.List;
59 import java.util.StringTokenizer;
60 import java.util.concurrent.Callable;
62 import javax.servlet.ServletContext;
63 import javax.servlet.ServletException;
64 import javax.servlet.ServletOutputStream;
65 import javax.servlet.http.HttpServletRequest;
66 import javax.servlet.http.HttpServletResponse;
68 import org.apache.commons.fileupload.FileItemIterator;
69 import org.apache.commons.fileupload.FileItemStream;
70 import org.apache.commons.fileupload.FileUploadException;
71 import org.apache.commons.fileupload.ProgressListener;
72 import org.apache.commons.fileupload.servlet.ServletFileUpload;
73 import org.apache.commons.fileupload.util.Streams;
74 import org.apache.commons.httpclient.util.DateParseException;
75 import org.apache.commons.httpclient.util.DateUtil;
76 import org.apache.commons.logging.Log;
77 import org.apache.commons.logging.LogFactory;
78 import org.json.JSONArray;
79 import org.json.JSONException;
80 import org.json.JSONObject;
84 * A class that handles operations on the 'files' namespace.
88 public class FilesHandler extends RequestHandler {
90 * The request parameter name for fetching a different version.
92 private static final String VERSION_PARAM = "version";
95 * The request attribute containing the owner of the destination URI
96 * in a copy or move request.
98 private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
100 private static final int TRACK_PROGRESS_PERCENT = 5;
103 * The form parameter name that contains the signature in a browser POST upload.
105 private static final String AUTHORIZATION_PARAMETER = "Authorization";
108 * The form parameter name that contains the date in a browser POST upload.
110 private static final String DATE_PARAMETER = "Date";
113 * The request parameter name for making an upload progress request.
115 private static final String PROGRESS_PARAMETER = "progress";
118 * The request parameter name for restoring a previous version of a file.
120 private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
125 private static Log logger = LogFactory.getLog(FilesHandler.class);
128 * The servlet context provided by the call site.
130 private ServletContext context;
133 * @param servletContext
135 public FilesHandler(ServletContext servletContext) {
136 context = servletContext;
140 * Serve the specified resource, optionally including the data content.
142 * @param req The servlet request we are processing
143 * @param resp The servlet response we are creating
144 * @param content Should the content be included?
146 * @exception IOException if an input/output error occurs
147 * @exception ServletException if a servlet-specified error occurs
148 * @throws RpcException
149 * @throws InsufficientPermissionsException
150 * @throws ObjectNotFoundException
153 protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
154 throws IOException, ServletException {
155 boolean authDeferred = getAuthDeferred(req);
156 String path = getInnerPath(req, PATH_FILES);
160 path = URLDecoder.decode(path, "UTF-8");
161 } catch (IllegalArgumentException e) {
162 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
165 String progress = req.getParameter(PROGRESS_PARAMETER);
167 if (logger.isDebugEnabled())
169 logger.debug("Serving resource '" + path + "' headers and data");
171 logger.debug("Serving resource '" + path + "' headers only");
173 User user = getUser(req);
174 User owner = getOwner(req);
175 if (user == null) user = owner;
176 boolean exists = true;
177 Object resource = null;
178 FileHeaderDTO file = null;
179 FolderDTO folder = null;
181 resource = getService().getResourceAtPath(owner.getId(), path, false);
182 } catch (ObjectNotFoundException e) {
184 } catch (RpcException e) {
185 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
191 // We do not want to leak information if the request
192 // was not authenticated.
193 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
196 // A request for upload progress.
197 if (progress != null && content) {
198 serveProgress(req, resp, progress, user, null);
202 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
206 if (resource instanceof FolderDTO)
207 folder = (FolderDTO) resource;
209 file = (FileHeaderDTO) resource;
211 // Now it's time to perform the deferred authentication check.
212 // Since regular signature checking was already performed,
213 // we need to check the read-all flag or the signature-in-parameters.
215 if (file != null && !file.isReadForAll() && content) {
216 // Check for GET with the signature in the request parameters.
217 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
218 String dateParam = req.getParameter(DATE_PARAMETER);
219 if (auth == null || dateParam == null) {
220 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
226 timestamp = DateUtil.parseDate(dateParam).getTime();
227 } catch (DateParseException e) {
228 resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
231 if (!isTimeValid(timestamp)) {
232 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
236 // Fetch the Authorization parameter and find the user specified in it.
237 String[] authParts = auth.split(" ");
238 if (authParts.length != 2) {
239 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
242 String username = authParts[0];
243 String signature = authParts[1];
246 user = getService().findUser(username);
247 } catch (RpcException e) {
248 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
252 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
255 req.setAttribute(USER_ATTRIBUTE, user);
257 // Remove the servlet path from the request URI.
258 String p = req.getRequestURI();
259 String servletPath = req.getContextPath() + req.getServletPath();
260 p = p.substring(servletPath.length());
261 // Validate the signature in the Authorization parameter.
262 String data = req.getMethod() + dateParam + p;
263 if (!isSignatureValid(signature, user, data)) {
264 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
267 } else if (file != null && !file.isReadForAll() || file == null) {
268 // Check for a read-for-all file request.
269 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
273 // If the resource is not a collection, and the resource path
274 // ends with "/" or "\", return NOT FOUND.
276 if (path.endsWith("/") || path.endsWith("\\")) {
277 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
281 // Workaround for IE's broken caching behavior.
283 resp.setHeader("Expires", "-1");
285 // A request for upload progress.
286 if (progress != null && content) {
288 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
291 serveProgress(req, resp, progress, user, file);
295 // Fetch the version to retrieve, if specified.
296 String verStr = req.getParameter(VERSION_PARAM);
298 FileBodyDTO oldBody = null;
299 if (verStr != null && file != null)
301 version = Integer.valueOf(verStr);
302 } catch (NumberFormatException e) {
303 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
308 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
309 } catch (RpcException e) {
310 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
312 } catch (ObjectNotFoundException e) {
313 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
315 } catch (InsufficientPermissionsException e) {
316 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
320 // Check if the conditions specified in the optional If headers are
321 // satisfied. Doing this for folders would require recursive checking
322 // for all of their children, which in turn would defy the purpose of
325 // Checking If headers.
326 if (!checkIfHeaders(req, resp, file, oldBody))
329 // Find content type.
330 String contentType = null;
332 contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
333 if (contentType == null) {
334 contentType = context.getMimeType(file.getName());
335 file.setMimeType(contentType);
338 contentType = "application/json;charset=UTF-8";
340 ArrayList ranges = null;
341 long contentLength = -1L;
344 // Parse range specifier.
345 ranges = parseRange(req, resp, file, oldBody);
347 resp.setHeader("ETag", getETag(file, oldBody));
348 // Last-Modified header.
349 String lastModified = oldBody == null ?
350 getLastModifiedHttp(file.getAuditInfo()) :
351 getLastModifiedHttp(oldBody.getAuditInfo());
352 resp.setHeader("Last-Modified", lastModified);
353 // X-GSS-Metadata header.
355 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
356 } catch (InsufficientPermissionsException e) {
357 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
360 // Get content length.
361 contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
362 // Special case for zero length files, which would cause a
363 // (silent) ISE when setting the output buffer size.
364 if (contentLength == 0L)
367 // Set the folder X-GSS-Metadata header.
369 resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
370 } catch (InsufficientPermissionsException e) {
371 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
375 ServletOutputStream ostream = null;
376 PrintWriter writer = null;
380 ostream = resp.getOutputStream();
381 } catch (IllegalStateException e) {
382 // If it fails, we try to get a Writer instead if we're
383 // trying to serve a text file
384 if ( contentType == null
385 || contentType.startsWith("text")
386 || contentType.endsWith("xml") )
387 writer = resp.getWriter();
393 || (ranges == null || ranges.isEmpty())
394 && req.getHeader("Range") == null
396 // Set the appropriate output headers
397 if (contentType != null) {
398 if (logger.isDebugEnabled())
399 logger.debug("contentType='" + contentType + "'");
400 resp.setContentType(contentType);
402 if (file != null && contentLength >= 0) {
403 if (logger.isDebugEnabled())
404 logger.debug("contentLength=" + contentLength);
405 if (contentLength < Integer.MAX_VALUE)
406 resp.setContentLength((int) contentLength);
408 // Set the content-length as String to be able to use a long
409 resp.setHeader("content-length", "" + contentLength);
412 InputStream renderResult = null;
415 // Serve the directory browser
417 renderResult = renderJson(user, folder);
418 } catch (InsufficientPermissionsException e) {
419 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
422 // Copy the input stream to our output stream (if requested)
425 resp.setBufferSize(output);
426 } catch (IllegalStateException e) {
431 if (needsContentDisposition(req))
432 resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+URLEncoder.encode(file.getName(),"UTF-8"));
434 resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+URLEncoder.encode(file.getName(),"UTF-8"));
436 copy(file, renderResult, ostream, req, oldBody);
438 copy(file, renderResult, writer, req, oldBody);
439 if (file!=null) getService().updateAccounting(owner, new Date(), contentLength);
440 } catch (ObjectNotFoundException e) {
441 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
443 } catch (InsufficientPermissionsException e) {
444 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
446 } catch (RpcException e) {
447 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
452 if (ranges == null || ranges.isEmpty())
454 // Partial content response.
455 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
457 if (ranges.size() == 1) {
458 Range range = (Range) ranges.get(0);
459 resp.addHeader("Content-Range", "bytes "
461 + "-" + range.end + "/"
463 long length = range.end - range.start + 1;
464 if (length < Integer.MAX_VALUE)
465 resp.setContentLength((int) length);
467 // Set the content-length as String to be able to use a long
468 resp.setHeader("content-length", "" + length);
470 if (contentType != null) {
471 if (logger.isDebugEnabled())
472 logger.debug("contentType='" + contentType + "'");
473 resp.setContentType(contentType);
478 resp.setBufferSize(output);
479 } catch (IllegalStateException e) {
484 copy(file, ostream, range, req, oldBody);
486 copy(file, writer, range, req, oldBody);
487 getService().updateAccounting(owner, new Date(), contentLength);
488 } catch (ObjectNotFoundException e) {
489 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
491 } catch (InsufficientPermissionsException e) {
492 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
494 } catch (RpcException e) {
495 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
500 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
503 resp.setBufferSize(output);
504 } catch (IllegalStateException e) {
509 copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
511 copy(file, writer, ranges.iterator(), contentType, req, oldBody);
512 getService().updateAccounting(owner, new Date(), contentLength);
513 } catch (ObjectNotFoundException e) {
514 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
516 } catch (InsufficientPermissionsException e) {
517 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
519 } catch (RpcException e) {
520 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
529 * Determines whether the user agent needs the Content-Disposition
530 * header to be set, in order to properly download a file.
532 * @param req the HTTP request
533 * @return true if the Content-Disposition HTTP header must be set
535 private boolean needsContentDisposition(HttpServletRequest req) {
536 /*String agent = req.getHeader("user-agent");
537 if (agent != null && agent.contains("MSIE"))
539 String dl = req.getParameter("dl");
546 * Sends a progress update on the amount of bytes received until now for
547 * a file that the current user is currently uploading.
549 * @param req the HTTP request
550 * @param resp the HTTP response
551 * @param parameter the value for the progress request parameter
552 * @param user the current user
553 * @param file the file being uploaded, or null if the request is about a new file
554 * @throws IOException if an I/O error occurs
556 private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
557 String parameter, User user, FileHeaderDTO file) throws IOException {
558 String filename = file == null ? parameter : file.getName();
560 FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
561 if (status == null) {
562 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
565 JSONObject json = new JSONObject();
566 json.put("bytesUploaded", status.getBytesUploaded()).
567 put("bytesTotal", status.getFileSize());
568 sendJson(req, resp, json.toString());
570 // Workaround for IE's broken caching behavior.
571 resp.setHeader("Expires", "-1");
573 } catch (RpcException e) {
574 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
576 } catch (JSONException e) {
577 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
583 * Server a POST request to create/modify a file or folder.
585 * @param req the HTTP request
586 * @param resp the HTTP response
587 * @exception IOException if an input/output error occurs
589 void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
590 boolean authDeferred = getAuthDeferred(req);
591 if (!authDeferred && req.getParameterMap().size() > 1) {
592 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
595 String path = getInnerPath(req, PATH_FILES);
596 path = path.endsWith("/")? path: path + '/';
598 path = URLDecoder.decode(path, "UTF-8");
599 } catch (IllegalArgumentException e) {
600 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
603 // We only defer authenticating multipart POST requests.
605 if (!ServletFileUpload.isMultipartContent(req)) {
606 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
609 handleMultipart(req, resp, path);
613 String newName = req.getParameter(NEW_FOLDER_PARAMETER);
614 boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
615 boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
616 boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
617 String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
618 String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
619 String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
622 createFolder(req, resp, path, newName);
623 else if (hasUpdateParam)
624 updateResource(req, resp, path);
625 else if (hasTrashParam)
626 trashResource(req, resp, path);
627 else if (hasRestoreParam)
628 restoreResource(req, resp, path);
629 else if (copyTo != null)
630 copyResource(req, resp, path, copyTo);
631 else if (moveTo != null)
632 moveResource(req, resp, path, moveTo);
633 else if (restoreVersion != null)
634 restoreVersion(req, resp, path, restoreVersion);
636 // IE with Gears uses POST for multiple uploads.
637 putResource(req, resp);
641 * Restores a previous version for a file.
643 * @param req the HTTP request
644 * @param resp the HTTP response
645 * @param path the resource path
646 * @param version the version number to restore
647 * @throws IOException if an I/O error occurs
649 private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
650 User user = getUser(req);
651 User owner = getOwner(req);
652 Object resource = null;
654 resource = getService().getResourceAtPath(owner.getId(), path, true);
655 } catch (ObjectNotFoundException e) {
656 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
658 } catch (RpcException e) {
659 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
662 if (resource instanceof FolderDTO) {
663 resp.sendError(HttpServletResponse.SC_CONFLICT);
668 FileHeaderDTO file = (FileHeaderDTO) resource;
669 int oldVersion = Integer.parseInt(version);
670 getService().restoreVersion(user.getId(), file.getId(), oldVersion);
671 } catch (InsufficientPermissionsException e) {
672 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
673 } catch (ObjectNotFoundException e) {
674 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
675 } catch (RpcException e) {
676 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
677 } catch (GSSIOException e) {
678 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
679 } catch (QuotaExceededException e) {
680 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
681 } catch (NumberFormatException e) {
682 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
687 * A method for handling multipart POST requests for uploading
688 * files from browser-based JavaScript clients.
690 * @param request the HTTP request
691 * @param response the HTTP response
692 * @param path the resource path
693 * @throws IOException in case an error occurs writing to the
696 private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
697 if (logger.isDebugEnabled())
698 logger.debug("Multipart POST for resource: " + path);
700 User owner = getOwner(request);
701 boolean exists = true;
702 Object resource = null;
703 FileHeaderDTO file = null;
705 resource = getService().getResourceAtPath(owner.getId(), path, false);
706 } catch (ObjectNotFoundException e) {
708 } catch (RpcException e) {
709 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
714 if (resource instanceof FileHeaderDTO) {
715 file = (FileHeaderDTO) resource;
716 if (file.isDeleted()) {
717 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
721 response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
725 FolderDTO folder = null;
727 String parentPath = null;
729 parentPath = getParentPath(path);
730 parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
731 } catch (ObjectNotFoundException e) {
732 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
734 } catch (RpcException e) {
735 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
738 if (!(parent instanceof FolderDTO)) {
739 response.sendError(HttpServletResponse.SC_CONFLICT);
742 folder = (FolderDTO) parent;
743 String fileName = getLastElement(path);
745 FileItemIterator iter;
746 File uploadedFile = null;
748 // Create a new file upload handler.
749 ServletFileUpload upload = new ServletFileUpload();
750 StatusProgressListener progressListener = new StatusProgressListener(getService());
751 upload.setProgressListener(progressListener);
752 iter = upload.getItemIterator(request);
753 String dateParam = null;
755 while (iter.hasNext()) {
756 FileItemStream item = iter.next();
757 String name = item.getFieldName();
758 InputStream stream = item.openStream();
759 if (item.isFormField()) {
760 final String value = Streams.asString(stream);
761 if (name.equals(DATE_PARAMETER))
763 else if (name.equals(AUTHORIZATION_PARAMETER))
766 if (logger.isDebugEnabled())
767 logger.debug(name + ":" + value);
769 // Fetch the timestamp used to guard against replay attacks.
770 if (dateParam == null) {
771 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
777 timestamp = DateUtil.parseDate(dateParam).getTime();
778 } catch (DateParseException e) {
779 response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
782 if (!isTimeValid(timestamp)) {
783 response.sendError(HttpServletResponse.SC_FORBIDDEN);
787 // Fetch the Authorization parameter and find the user specified in it.
789 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
792 String[] authParts = auth.split(" ");
793 if (authParts.length != 2) {
794 response.sendError(HttpServletResponse.SC_FORBIDDEN);
797 String username = authParts[0];
798 String signature = authParts[1];
801 user = getService().findUser(username);
802 } catch (RpcException e) {
803 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
807 response.sendError(HttpServletResponse.SC_FORBIDDEN);
810 request.setAttribute(USER_ATTRIBUTE, user);
812 // Remove the servlet path from the request URI.
813 String p = request.getRequestURI();
814 String servletPath = request.getContextPath() + request.getServletPath();
815 p = p.substring(servletPath.length());
816 // Validate the signature in the Authorization parameter.
817 String data = request.getMethod() + dateParam + p;
818 if (!isSignatureValid(signature, user, data)) {
819 response.sendError(HttpServletResponse.SC_FORBIDDEN);
823 progressListener.setUserId(user.getId());
824 progressListener.setFilename(fileName);
825 String contentType = item.getContentType();
828 uploadedFile = getService().uploadFile(stream, user.getId());
829 } catch (IOException ex) {
830 throw new GSSIOException(ex, false);
832 FileHeaderDTO fileDTO = null;
834 fileDTO = getService().createFile(user.getId(), folder.getId(), fileName, contentType, uploadedFile.getCanonicalFile().length(), uploadedFile.getAbsolutePath());
836 fileDTO = getService().updateFileContents(user.getId(), file.getId(), contentType, uploadedFile.getCanonicalFile().length(), uploadedFile.getAbsolutePath());
837 getService().updateAccounting(owner, new Date(), fileDTO.getFileSize());
838 getService().removeFileUploadProgress(user.getId(), fileName);
841 // We can't return 204 here since GWT's onSubmitComplete won't fire.
842 response.setContentType("text/html");
843 response.getWriter().print("<pre></pre>");
844 } catch (FileUploadException e) {
845 String error = "Error while uploading file";
846 logger.error(error, e);
847 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
848 } catch (GSSIOException e) {
849 if (uploadedFile != null && uploadedFile.exists())
850 uploadedFile.delete();
851 String error = "Error while uploading file";
853 logger.error(error, e);
855 logger.debug(error, e);
856 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
857 } catch (DuplicateNameException e) {
858 if (uploadedFile != null && uploadedFile.exists())
859 uploadedFile.delete();
860 String error = "The specified file name already exists in this folder";
861 logger.error(error, e);
862 response.sendError(HttpServletResponse.SC_CONFLICT, error);
864 } catch (InsufficientPermissionsException e) {
865 if (uploadedFile != null && uploadedFile.exists())
866 uploadedFile.delete();
867 String error = "You don't have the necessary permissions";
868 logger.error(error, e);
869 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
871 } catch (QuotaExceededException e) {
872 if (uploadedFile != null && uploadedFile.exists())
873 uploadedFile.delete();
874 String error = "Not enough free space available";
875 if (logger.isDebugEnabled())
876 logger.debug(error, e);
877 response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
879 } catch (ObjectNotFoundException e) {
880 if (uploadedFile != null && uploadedFile.exists())
881 uploadedFile.delete();
882 String error = "A specified object was not found";
883 logger.error(error, e);
884 response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
885 } catch (RpcException e) {
886 if (uploadedFile != null && uploadedFile.exists())
887 uploadedFile.delete();
888 String error = "An error occurred while communicating with the service";
889 logger.error(error, e);
890 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
894 private String getBackupFilename(FileHeaderDTO file, String filename){
895 List<FileHeaderDTO> deletedFiles = new ArrayList<FileHeaderDTO>();
897 deletedFiles = getService().getDeletedFiles(file.getOwner().getId());
899 catch(ObjectNotFoundException e){
901 } catch (RpcException e) {
904 List<FileHeaderDTO> filesInSameFolder = new ArrayList<FileHeaderDTO>();
905 for(FileHeaderDTO deleted : deletedFiles)
906 if(deleted.getFolder().getId().equals(file.getFolder().getId()))
907 filesInSameFolder.add(deleted);
909 String filenameToCheck = filename;
910 for(FileHeaderDTO same : filesInSameFolder)
911 if(same.getName().startsWith(filename)){
912 String toCheck=same.getName().substring(filename.length(),same.getName().length());
913 if(toCheck.startsWith(" ")){
916 test = Integer.valueOf(toCheck.replace(" ",""));
918 catch(NumberFormatException e){
919 //do nothing since string is not a number
926 return filename+" "+i;
930 * Move the resource in the specified path to the specified destination.
932 * @param req the HTTP request
933 * @param resp the HTTP response
934 * @param path the path of the resource
935 * @param moveTo the destination of the move procedure
936 * @throws IOException if an input/output error occurs
938 private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
939 User user = getUser(req);
940 User owner = getOwner(req);
941 Object resource = null;
943 resource = getService().getResourceAtPath(owner.getId(), path, true);
944 } catch (ObjectNotFoundException e) {
945 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
947 } catch (RpcException e) {
948 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
952 String destination = null;
953 User destOwner = null;
954 boolean exists = true;
956 destination = getDestinationPath(req, encodePath(moveTo));
957 destination = URLDecoder.decode(destination, "UTF-8");
958 destOwner = getDestinationOwner(req);
959 getService().getResourceAtPath(destOwner.getId(), destination, true);
960 } catch (ObjectNotFoundException e) {
962 } catch (URISyntaxException e) {
963 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
965 } catch (RpcException e) {
966 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
970 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
975 if (resource instanceof FolderDTO) {
976 FolderDTO folder = (FolderDTO) resource;
977 getService().moveFolderToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
979 FileHeaderDTO file = (FileHeaderDTO) resource;
980 getService().moveFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
982 } catch (InsufficientPermissionsException e) {
983 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
984 } catch (ObjectNotFoundException e) {
985 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
986 } catch (RpcException e) {
987 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
988 } catch (DuplicateNameException e) {
989 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
990 } catch (GSSIOException e) {
991 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
992 } catch (QuotaExceededException e) {
993 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
998 * Copy the resource in the specified path to the specified destination.
1000 * @param req the HTTP request
1001 * @param resp the HTTP response
1002 * @param path the path of the resource
1003 * @param copyTo the destination of the copy procedure
1004 * @throws IOException if an input/output error occurs
1006 private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1007 User user = getUser(req);
1008 User owner = getOwner(req);
1009 Object resource = null;
1011 resource = getService().getResourceAtPath(owner.getId(), path, true);
1012 } catch (ObjectNotFoundException e) {
1013 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1015 } catch (RpcException e) {
1016 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1020 String destination = null;
1021 User destOwner = null;
1022 boolean exists = true;
1024 String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1025 destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1026 destOwner = getDestinationOwner(req);
1027 getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1028 } catch (ObjectNotFoundException e) {
1030 } catch (URISyntaxException e) {
1031 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1033 } catch (RpcException e) {
1034 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1038 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1043 if (resource instanceof FolderDTO) {
1044 FolderDTO folder = (FolderDTO) resource;
1045 getService().copyFolderStructureToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
1047 FileHeaderDTO file = (FileHeaderDTO) resource;
1048 getService().copyFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
1050 } catch (InsufficientPermissionsException e) {
1051 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1052 } catch (ObjectNotFoundException e) {
1053 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1054 } catch (RpcException e) {
1055 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1056 } catch (DuplicateNameException e) {
1057 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1058 } catch (GSSIOException e) {
1059 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1060 } catch (QuotaExceededException e) {
1061 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1065 private String encodePath(String path) throws UnsupportedEncodingException{
1066 StringTokenizer str = new StringTokenizer(path, "/:", true);
1067 String result = new String();
1068 while(str.hasMoreTokens()){
1069 String token = str.nextToken();
1070 if(!token.equals("/") && !token.equals(":"))
1071 token = URLEncoder.encode(token,"UTF-8");
1072 result = result + token;
1077 * A helper method that extracts the relative resource path,
1078 * after removing the 'files' namespace.
1079 * The path returned is <i>not</i> URL-decoded.
1081 * @param req the HTTP request
1082 * @param path the specified path
1083 * @return the path relative to the root folder
1084 * @throws URISyntaxException
1085 * @throws RpcException in case an error occurs while communicating
1087 * @throws UnsupportedEncodingException
1089 private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1090 URI uri = new URI(path);
1091 String dest = uri.getRawPath();
1092 // Remove the context path from the destination URI.
1093 String contextPath = req.getContextPath();
1094 if (!dest.startsWith(contextPath))
1095 throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1096 dest = dest.substring(contextPath.length());
1097 // Remove the servlet path from the destination URI.
1098 String servletPath = req.getServletPath();
1099 if (!dest.startsWith(servletPath))
1100 throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1101 dest = dest.substring(servletPath.length());
1102 // Strip the username part
1103 if (dest.length() < 2)
1104 throw new URISyntaxException(dest, "No username in the destination URI");
1105 int slash = dest.substring(1).indexOf('/');
1107 throw new URISyntaxException(dest, "No username in the destination URI");
1108 // Decode the user to get the proper characters (mainly the @)
1109 String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1111 o = getService().findUser(owner);
1113 throw new URISyntaxException(dest, "User " + owner + " not found");
1115 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1116 dest = dest.substring(slash + 1);
1118 // Chop the resource namespace part
1119 dest = dest.substring(RequestHandler.PATH_FILES.length());
1121 dest = dest.endsWith("/")? dest: dest + '/';
1126 * Move the resource in the specified path to the trash bin.
1128 * @param req the HTTP request
1129 * @param resp the HTTP response
1130 * @param path the path of the resource
1131 * @throws IOException if an input/output error occurs
1133 private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1134 User user = getUser(req);
1135 User owner = getOwner(req);
1136 Object resource = null;
1138 resource = getService().getResourceAtPath(owner.getId(), path, true);
1139 } catch (ObjectNotFoundException e) {
1140 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1142 } catch (RpcException e) {
1143 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1148 if (resource instanceof FolderDTO) {
1149 FolderDTO folder = (FolderDTO) resource;
1150 getService().moveFolderToTrash(user.getId(), folder.getId());
1152 FileHeaderDTO file = (FileHeaderDTO) resource;
1153 getService().moveFileToTrash(user.getId(), file.getId());
1155 } catch (InsufficientPermissionsException e) {
1156 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1157 } catch (ObjectNotFoundException e) {
1158 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1159 } catch (RpcException e) {
1160 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1165 * Restore the resource in the specified path from the trash bin.
1167 * @param req the HTTP request
1168 * @param resp the HTTP response
1169 * @param path the path of the resource
1170 * @throws IOException if an input/output error occurs
1172 private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1173 User user = getUser(req);
1174 User owner = getOwner(req);
1175 Object resource = null;
1177 resource = getService().getResourceAtPath(owner.getId(), path, false);
1178 } catch (ObjectNotFoundException e) {
1179 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1181 } catch (RpcException e) {
1182 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1187 if (resource instanceof FolderDTO) {
1188 FolderDTO folder = (FolderDTO) resource;
1189 getService().removeFolderFromTrash(user.getId(), folder.getId());
1191 FileHeaderDTO file = (FileHeaderDTO) resource;
1192 getService().removeFileFromTrash(user.getId(), file.getId());
1194 } catch (InsufficientPermissionsException e) {
1195 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1196 } catch (ObjectNotFoundException e) {
1197 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1198 } catch (RpcException e) {
1199 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1204 * Update the resource in the specified path.
1206 * @param req the HTTP request
1207 * @param resp the HTTP response
1208 * @param path the path of the resource
1209 * @throws IOException if an input/output error occurs
1211 private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1212 final User user = getUser(req);
1213 User owner = getOwner(req);
1214 Object resource = null;
1216 resource = getService().getResourceAtPath(owner.getId(), path, false);
1217 } catch (ObjectNotFoundException e) {
1218 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1220 } catch (RpcException e) {
1221 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1224 //use utf-8 encoding for reading request
1225 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1226 StringBuffer input = new StringBuffer();
1228 JSONObject json = null;
1229 while ((line = reader.readLine()) != null)
1233 json = new JSONObject(input.toString());
1234 if (logger.isDebugEnabled())
1235 logger.debug("JSON update: " + json);
1236 if (resource instanceof FolderDTO) {
1237 final FolderDTO folder = (FolderDTO) resource;
1238 String name = json.optString("name");
1239 if (!name.isEmpty()){
1241 name = URLDecoder.decode(name, "UTF-8");
1242 } catch (IllegalArgumentException e) {
1243 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1246 final String fName = name;
1247 FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1249 public FolderDTO call() throws Exception {
1250 return getService().modifyFolder(user.getId(), folder.getId(), fName);
1254 String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1255 String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1256 parentUrl = parentUrl.replaceAll(fpath, "");
1257 if(!parentUrl.endsWith("/"))
1258 parentUrl = parentUrl+"/";
1259 parentUrl = parentUrl+folderUpdated.getOwner().getUsername()+PATH_FILES+folderUpdated.getPath();
1260 resp.getWriter().println(parentUrl);
1263 JSONArray permissions = json.optJSONArray("permissions");
1264 if (permissions != null) {
1265 final Set<PermissionDTO> perms = parsePermissions(user, permissions);
1266 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1268 public Object call() throws Exception {
1269 getService().setFolderPermissions(user.getId(), folder.getId(), perms);
1276 final FileHeaderDTO file = (FileHeaderDTO) resource;
1278 if (json.opt("name") != null)
1279 name = json.optString("name");
1280 JSONArray tagset = json.optJSONArray("tags");
1282 StringBuffer t = new StringBuffer();
1283 if (tagset != null) {
1284 for (int i = 0; i < tagset.length(); i++)
1285 t.append(tagset.getString(i) + ',');
1286 tags = t.toString();
1288 if (name != null || tags != null) {
1289 final String fName = name;
1290 final String fTags = tags;
1291 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1293 public Object call() throws Exception {
1294 getService().updateFile(user.getId(), file.getId(), fName, fTags);
1301 JSONArray permissions = json.optJSONArray("permissions");
1302 Set<PermissionDTO> perms = null;
1303 if (permissions != null)
1304 perms = parsePermissions(user, permissions);
1305 Boolean readForAll = null;
1306 if (json.opt("readForAll") != null)
1307 readForAll = json.optBoolean("readForAll");
1308 if (perms != null || readForAll != null) {
1309 final Boolean fReadForAll = readForAll;
1310 final Set<PermissionDTO> fPerms = perms;
1311 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1313 public Object call() throws Exception {
1314 getService().setFilePermissions(user.getId(), file.getId(), fReadForAll, fPerms);
1321 if (json.opt("versioned") != null) {
1322 final boolean versioned = json.getBoolean("versioned");
1323 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1325 public Object call() throws Exception {
1326 getService().toggleFileVersioning(user.getId(), file.getId(), versioned);
1333 } catch (JSONException e) {
1334 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1335 } catch (InsufficientPermissionsException e) {
1336 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1337 } catch (ObjectNotFoundException e) {
1338 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1339 } catch (DuplicateNameException e) {
1340 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1341 } catch (RpcException e) {
1342 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1343 } catch (Exception e) {
1344 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1350 * Helper method to convert a JSON array of permissions into a set of
1351 * PermissionDTO objects.
1353 * @param user the current user
1354 * @param permissions the JSON array to parse
1355 * @return the parsed set of permissions
1356 * @throws JSONException if there was an error parsing the JSON object
1357 * @throws RpcException if there was an error communicating with the EJB
1358 * @throws ObjectNotFoundException if the user could not be found
1360 private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1361 throws JSONException, RpcException, ObjectNotFoundException {
1362 if (permissions == null)
1364 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1365 for (int i = 0; i < permissions.length(); i++) {
1366 JSONObject j = permissions.getJSONObject(i);
1367 PermissionDTO perm = new PermissionDTO();
1368 perm.setModifyACL(j.optBoolean("modifyACL"));
1369 perm.setRead(j.optBoolean("read"));
1370 perm.setWrite(j.optBoolean("write"));
1371 String permUser = j.optString("user");
1372 if (!permUser.isEmpty()) {
1373 User u = getService().findUser(permUser);
1375 throw new ObjectNotFoundException("User " + permUser + " not found");
1376 perm.setUser(u.getDTO());
1378 String permGroup = j.optString("group");
1379 if (!permGroup.isEmpty()) {
1380 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1383 if (permUser.isEmpty() && permGroup.isEmpty() ||
1384 permUser.isEmpty() && permGroup.isEmpty())
1385 throw new JSONException("A permission must correspond to either a user or a group");
1392 * Creates a new folder with the specified name under the folder in the provided path.
1394 * @param req the HTTP request
1395 * @param resp the HTTP response
1396 * @param path the parent folder path
1397 * @param folderName the name of the new folder
1398 * @throws IOException if an input/output error occurs
1400 private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1401 if (logger.isDebugEnabled())
1402 logger.debug("Creating folder " + folderName + " in '" + path);
1404 final User user = getUser(req);
1405 User owner = getOwner(req);
1406 boolean exists = true;
1408 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1409 } catch (ObjectNotFoundException e) {
1411 } catch (RpcException e) {
1412 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1417 resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1418 ", " + METHOD_HEAD);
1419 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1425 parent = getService().getResourceAtPath(owner.getId(), path, true);
1426 } catch (ObjectNotFoundException e) {
1427 resp.sendError(HttpServletResponse.SC_CONFLICT);
1429 } catch (RpcException e) {
1430 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1434 if (parent instanceof FolderDTO) {
1435 final FolderDTO folder = (FolderDTO) parent;
1436 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1438 public Object call() throws Exception {
1439 getService().createFolder(user.getId(), folder.getId(), folderName);
1444 String newResource = getContextPath(req, true) + folderName;
1445 resp.setHeader("Location", newResource);
1446 resp.setContentType("text/plain");
1447 PrintWriter out = resp.getWriter();
1448 out.println(newResource);
1450 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1453 } catch (DuplicateNameException e) {
1454 resp.sendError(HttpServletResponse.SC_CONFLICT);
1456 } catch (InsufficientPermissionsException e) {
1457 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1459 } catch (ObjectNotFoundException e) {
1460 resp.sendError(HttpServletResponse.SC_CONFLICT);
1462 } catch (RpcException e) {
1463 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1465 } catch (Exception e) {
1466 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1469 resp.setStatus(HttpServletResponse.SC_CREATED);
1475 * @throws IOException
1476 * @throws FileNotFoundException
1478 void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1479 String path = getInnerPath(req, PATH_FILES);
1481 path = URLDecoder.decode(path, "UTF-8");
1482 } catch (IllegalArgumentException e) {
1483 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1486 if (logger.isDebugEnabled())
1487 logger.debug("Updating resource: " + path);
1489 final User user = getUser(req);
1490 User owner = getOwner(req);
1491 boolean exists = true;
1492 Object resource = null;
1493 FileHeaderDTO file = null;
1495 resource = getService().getResourceAtPath(owner.getId(), path, false);
1496 } catch (ObjectNotFoundException e) {
1498 } catch (RpcException e) {
1499 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1504 if (resource instanceof FileHeaderDTO)
1505 file = (FileHeaderDTO) resource;
1507 resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1510 boolean result = true;
1512 // Temporary content file used to support partial PUT.
1513 File contentFile = null;
1515 Range range = parseContentRange(req, resp);
1517 InputStream resourceInputStream = null;
1519 // Append data specified in ranges to existing content for this
1520 // resource - create a temporary file on the local filesystem to
1521 // perform this operation.
1522 // Assume just one range is specified for now
1523 if (range != null) {
1525 contentFile = executePartialPut(req, range, path);
1526 } catch (RpcException e) {
1527 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1529 } catch (ObjectNotFoundException e) {
1530 resp.sendError(HttpServletResponse.SC_CONFLICT);
1532 } catch (InsufficientPermissionsException e) {
1533 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1536 resourceInputStream = new FileInputStream(contentFile);
1538 resourceInputStream = req.getInputStream();
1541 FolderDTO folder = null;
1542 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1543 if (!(parent instanceof FolderDTO)) {
1544 resp.sendError(HttpServletResponse.SC_CONFLICT);
1547 folder = (FolderDTO) parent;
1548 final String name = getLastElement(path);
1549 final String mimeType = context.getMimeType(name);
1550 File uploadedFile = null;
1552 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1553 } catch (IOException ex) {
1554 throw new GSSIOException(ex, false);
1556 FileHeaderDTO fileDTO = null;
1558 fileDTO = getService().updateFileContents(user.getId(), file.getId(), mimeType, uploadedFile.getCanonicalFile().length(), uploadedFile.getAbsolutePath());
1560 final File uploadedf = uploadedFile;
1561 final FolderDTO parentf = folder;
1562 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1564 public FileHeaderDTO call() throws Exception {
1565 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1570 getService().updateAccounting(owner, new Date(), fileDTO.getFileSize());
1571 getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1572 } catch(ObjectNotFoundException e) {
1574 } catch (RpcException e) {
1575 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1577 } catch (IOException e) {
1578 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1580 } catch (GSSIOException e) {
1581 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1583 } catch (DuplicateNameException e) {
1584 resp.sendError(HttpServletResponse.SC_CONFLICT);
1586 } catch (InsufficientPermissionsException e) {
1587 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1589 } catch (QuotaExceededException e) {
1590 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1592 } catch (Exception e) {
1593 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1599 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1601 resp.setStatus(HttpServletResponse.SC_CREATED);
1603 resp.sendError(HttpServletResponse.SC_CONFLICT);
1607 * Delete a resource.
1609 * @param req The servlet request we are processing
1610 * @param resp The servlet response we are processing
1611 * @throws IOException if the response cannot be sent
1613 void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1614 String path = getInnerPath(req, PATH_FILES);
1615 if (logger.isDebugEnabled())
1616 logger.debug("Deleting resource '" + path);
1617 path = URLDecoder.decode(path, "UTF-8");
1618 User user = getUser(req);
1619 User owner = getOwner(req);
1620 boolean exists = true;
1621 Object object = null;
1623 object = getService().getResourceAtPath(owner.getId(), path, false);
1624 } catch (ObjectNotFoundException e) {
1626 } catch (RpcException e) {
1627 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1632 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1636 FolderDTO folder = null;
1637 FileHeaderDTO file = null;
1638 if (object instanceof FolderDTO)
1639 folder = (FolderDTO) object;
1641 file = (FileHeaderDTO) object;
1645 getService().deleteFile(user.getId(), file.getId());
1646 } catch (InsufficientPermissionsException e) {
1647 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1649 } catch (ObjectNotFoundException e) {
1650 // Although we had already found the object, it was
1651 // probably deleted from another thread.
1652 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1654 } catch (RpcException e) {
1655 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1658 else if (folder != null)
1660 getService().deleteFolder(user.getId(), folder.getId());
1661 } catch (InsufficientPermissionsException e) {
1662 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1664 } catch (ObjectNotFoundException e) {
1665 resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1667 } catch (RpcException e) {
1668 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1671 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1676 * Return an InputStream to a JSON representation of the contents
1677 * of this directory.
1679 * @param user the user that made the request
1680 * @param folder the specified directory
1681 * @return an input stream with the rendered contents
1682 * @throws IOException if the response cannot be sent
1683 * @throws ServletException
1684 * @throws InsufficientPermissionsException if the user does not have
1685 * the necessary privileges to read the directory
1687 private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1688 ServletException, InsufficientPermissionsException {
1689 JSONObject json = new JSONObject();
1691 json.put("name", folder.getName()).
1692 put("owner", folder.getOwner().getUsername()).
1693 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1694 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1695 put("deleted", folder.isDeleted());
1696 if (folder.getAuditInfo().getModifiedBy() != null)
1697 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1698 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1699 if (folder.getParent() != null) {
1700 JSONObject j = new JSONObject();
1701 j.put("uri", getApiRoot() + folder.getParent().getURI());
1702 j.put("name", folder.getParent().getName());
1703 json.put("parent", j);
1705 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1706 for (FolderDTO f: folder.getSubfolders())
1707 if (!f.isDeleted()) {
1708 JSONObject j = new JSONObject();
1709 j.put("name", f.getName()).
1710 put("uri", getApiRoot() + f.getURI());
1713 json.put("folders", subfolders);
1714 List<JSONObject> files = new ArrayList<JSONObject>();
1715 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1716 for (FileHeaderDTO f: fileHeaders) {
1717 JSONObject j = new JSONObject();
1718 j.put("name", f.getName()).
1719 put("owner", f.getOwner().getUsername()).
1720 put("deleted", f.isDeleted()).
1721 put("version", f.getVersion()).
1722 put("content", f.getMimeType()).
1723 put("size", f.getFileSize()).
1724 put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1725 put("path", f.getFolder().getPath()).
1726 put("uri", getApiRoot() + f.getURI());
1727 if (f.getAuditInfo().getModificationDate() != null)
1728 j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1731 json.put("files", files);
1732 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1733 json.put("permissions", renderJson(perms));
1734 } catch (JSONException e) {
1735 throw new ServletException(e);
1736 } catch (ObjectNotFoundException e) {
1737 throw new ServletException(e);
1738 } catch (RpcException e) {
1739 throw new ServletException(e);
1742 // Prepare a writer to a buffered area
1743 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1744 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1745 PrintWriter writer = new PrintWriter(osWriter);
1747 // Return an input stream to the underlying bytes
1748 writer.write(json.toString());
1750 return new ByteArrayInputStream(stream.toByteArray());
1754 * Return a String with a JSON representation of the metadata
1755 * of the specified folder.
1756 * @throws RpcException
1757 * @throws InsufficientPermissionsException
1758 * @throws ObjectNotFoundException
1760 private String renderJsonMetadata(User user, FolderDTO folder)
1761 throws ServletException, InsufficientPermissionsException {
1762 // Check if the user has read permission.
1764 if (!getService().canReadFolder(user.getId(), folder.getId()))
1765 throw new InsufficientPermissionsException();
1766 } catch (ObjectNotFoundException e) {
1767 throw new ServletException(e);
1768 } catch (RpcException e) {
1769 throw new ServletException(e);
1772 JSONObject json = new JSONObject();
1774 json.put("name", folder.getName()).
1775 put("owner", folder.getOwner().getUsername()).
1776 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1777 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1778 put("deleted", folder.isDeleted());
1779 if (folder.getAuditInfo().getModifiedBy() != null)
1780 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1781 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1782 } catch (JSONException e) {
1783 throw new ServletException(e);
1785 return json.toString();
1789 * Return a String with a JSON representation of the metadata
1790 * of the specified file. If an old file body is provided, then
1791 * the metadata of that particular version will be returned.
1793 * @param user the user that made the request
1794 * @param file the specified file header
1795 * @param oldBody the version number
1796 * @return the JSON-encoded file
1797 * @throws ServletException
1798 * @throws InsufficientPermissionsException if the user does not have
1799 * the necessary privileges to read the directory
1801 private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
1802 throws ServletException, InsufficientPermissionsException {
1803 JSONObject json = new JSONObject();
1805 // Need to encode file name in order to properly display it in the web client.
1806 json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
1807 put("owner", file.getOwner().getUsername()).
1808 put("versioned", file.isVersioned()).
1809 put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
1810 put("readForAll", file.isReadForAll()).
1811 put("tags", renderJson(file.getTags())).
1812 put("path", file.getFolder().getPath()).
1813 put("uri", getApiRoot() + file.getURI()).
1814 put("deleted", file.isDeleted());
1815 JSONObject j = new JSONObject();
1816 j.put("uri", getApiRoot() + file.getFolder().getURI()).
1817 put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
1818 json.put("folder", j);
1819 if (oldBody != null)
1820 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
1821 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
1822 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
1823 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
1824 put("content", oldBody.getMimeType()).
1825 put("size", oldBody.getFileSize());
1827 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
1828 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
1829 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
1830 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
1831 put("content", file.getMimeType()).
1832 put("size", file.getFileSize());
1833 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
1834 json.put("permissions", renderJson(perms));
1835 } catch (JSONException e) {
1836 throw new ServletException(e);
1837 } catch (ObjectNotFoundException e) {
1838 throw new ServletException(e);
1839 } catch (RpcException e) {
1840 throw new ServletException(e);
1841 } catch (UnsupportedEncodingException e) {
1842 throw new ServletException(e);
1845 return json.toString();
1849 * Return a String with a JSON representation of the
1850 * specified set of permissions.
1852 * @param permissions the set of permissions
1853 * @return the JSON-encoded object
1854 * @throws JSONException
1855 * @throws UnsupportedEncodingException
1857 private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
1858 JSONArray perms = new JSONArray();
1859 for (PermissionDTO p: permissions) {
1860 JSONObject permission = new JSONObject();
1861 permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
1862 if (p.getUser() != null)
1863 permission.put("user", p.getUser().getUsername());
1864 if (p.getGroup() != null)
1865 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
1866 perms.put(permission);
1872 * Return a String with a JSON representation of the
1873 * specified collection of tags.
1875 * @param tags the collection of tags
1876 * @return the JSON-encoded object
1877 * @throws JSONException
1878 * @throws UnsupportedEncodingException
1880 private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
1881 JSONArray tagArray = new JSONArray();
1882 for (String t: tags)
1883 tagArray.put(URLEncoder.encode(t,"UTF-8"));
1888 * Retrieves the user who owns the destination namespace, for a
1889 * copy or move request.
1891 * @param req the HTTP request
1892 * @return the owner of the namespace
1894 protected User getDestinationOwner(HttpServletRequest req) {
1895 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
1899 * A helper inner class for updating the progress status of a file upload.
1903 public static class StatusProgressListener implements ProgressListener {
1904 private int percentLogged = 0;
1905 private long bytesTransferred = 0;
1907 private long fileSize = -100;
1909 private long tenKBRead = -1;
1911 private Long userId;
1913 private String filename;
1915 private ExternalAPI service;
1917 public StatusProgressListener(ExternalAPI aService) {
1922 * Modify the userId.
1924 * @param aUserId the userId to set
1926 public void setUserId(Long aUserId) {
1931 * Modify the filename.
1933 * @param aFilename the filename to set
1935 public void setFilename(String aFilename) {
1936 filename = aFilename;
1939 public void update(long bytesRead, long contentLength, int items) {
1940 //monitoring per percent of bytes uploaded
1941 bytesTransferred = bytesRead;
1942 if (fileSize != contentLength)
1943 fileSize = contentLength;
1944 int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
1946 if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
1947 if (percent != percentLogged){
1948 percentLogged = percent;
1950 if (userId != null && filename != null)
1951 service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
1952 } catch (ObjectNotFoundException e) {
1953 // Swallow the exception since it is going to be caught
1954 // by previously called methods