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 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
640 * Restores a previous version for a file.
642 * @param req the HTTP request
643 * @param resp the HTTP response
644 * @param path the resource path
645 * @param version the version number to restore
646 * @throws IOException if an I/O error occurs
648 private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
649 User user = getUser(req);
650 User owner = getOwner(req);
651 Object resource = null;
653 resource = getService().getResourceAtPath(owner.getId(), path, true);
654 } catch (ObjectNotFoundException e) {
655 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
657 } catch (RpcException e) {
658 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
661 if (resource instanceof FolderDTO) {
662 resp.sendError(HttpServletResponse.SC_CONFLICT);
667 FileHeaderDTO file = (FileHeaderDTO) resource;
668 int oldVersion = Integer.parseInt(version);
669 getService().restoreVersion(user.getId(), file.getId(), oldVersion);
670 } catch (InsufficientPermissionsException e) {
671 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
672 } catch (ObjectNotFoundException e) {
673 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
674 } catch (RpcException e) {
675 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
676 } catch (GSSIOException e) {
677 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
678 } catch (QuotaExceededException e) {
679 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
680 } catch (NumberFormatException e) {
681 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
686 * A method for handling multipart POST requests for uploading
687 * files from browser-based JavaScript clients.
689 * @param request the HTTP request
690 * @param response the HTTP response
691 * @param path the resource path
692 * @throws IOException in case an error occurs writing to the
695 private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
696 if (logger.isDebugEnabled())
697 logger.debug("Multipart POST for resource: " + path);
699 User owner = getOwner(request);
700 boolean exists = true;
701 Object resource = null;
702 FileHeaderDTO file = null;
704 resource = getService().getResourceAtPath(owner.getId(), path, false);
705 } catch (ObjectNotFoundException e) {
707 } catch (RpcException e) {
708 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
713 if (resource instanceof FileHeaderDTO) {
714 file = (FileHeaderDTO) resource;
715 if (file.isDeleted()) {
716 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
720 response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
724 FolderDTO folder = null;
726 String parentPath = null;
728 parentPath = getParentPath(path);
729 parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
730 } catch (ObjectNotFoundException e) {
731 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
733 } catch (RpcException e) {
734 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
737 if (!(parent instanceof FolderDTO)) {
738 response.sendError(HttpServletResponse.SC_CONFLICT);
741 folder = (FolderDTO) parent;
742 String fileName = getLastElement(path);
744 FileItemIterator iter;
745 File uploadedFile = null;
747 // Create a new file upload handler.
748 ServletFileUpload upload = new ServletFileUpload();
749 StatusProgressListener progressListener = new StatusProgressListener(getService());
750 upload.setProgressListener(progressListener);
751 iter = upload.getItemIterator(request);
752 String dateParam = null;
754 while (iter.hasNext()) {
755 FileItemStream item = iter.next();
756 String name = item.getFieldName();
757 InputStream stream = item.openStream();
758 if (item.isFormField()) {
759 final String value = Streams.asString(stream);
760 if (name.equals(DATE_PARAMETER))
762 else if (name.equals(AUTHORIZATION_PARAMETER))
765 if (logger.isDebugEnabled())
766 logger.debug(name + ":" + value);
768 // Fetch the timestamp used to guard against replay attacks.
769 if (dateParam == null) {
770 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
776 timestamp = DateUtil.parseDate(dateParam).getTime();
777 } catch (DateParseException e) {
778 response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
781 if (!isTimeValid(timestamp)) {
782 response.sendError(HttpServletResponse.SC_FORBIDDEN);
786 // Fetch the Authorization parameter and find the user specified in it.
788 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
791 String[] authParts = auth.split(" ");
792 if (authParts.length != 2) {
793 response.sendError(HttpServletResponse.SC_FORBIDDEN);
796 String username = authParts[0];
797 String signature = authParts[1];
800 user = getService().findUser(username);
801 } catch (RpcException e) {
802 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
806 response.sendError(HttpServletResponse.SC_FORBIDDEN);
809 request.setAttribute(USER_ATTRIBUTE, user);
811 // Remove the servlet path from the request URI.
812 String p = request.getRequestURI();
813 String servletPath = request.getContextPath() + request.getServletPath();
814 p = p.substring(servletPath.length());
815 // Validate the signature in the Authorization parameter.
816 String data = request.getMethod() + dateParam + p;
817 if (!isSignatureValid(signature, user, data)) {
818 response.sendError(HttpServletResponse.SC_FORBIDDEN);
822 progressListener.setUserId(user.getId());
823 progressListener.setFilename(fileName);
824 String contentType = item.getContentType();
827 uploadedFile = getService().uploadFile(stream, user.getId());
828 } catch (IOException ex) {
829 throw new GSSIOException(ex, false);
831 FileHeaderDTO fileDTO = null;
833 fileDTO = getService().createFile(user.getId(), folder.getId(), fileName, contentType, uploadedFile.getCanonicalFile().length(), uploadedFile.getAbsolutePath());
835 fileDTO = getService().updateFileContents(user.getId(), file.getId(), contentType, uploadedFile.getCanonicalFile().length(), uploadedFile.getAbsolutePath());
836 getService().updateAccounting(owner, new Date(), fileDTO.getFileSize());
837 getService().removeFileUploadProgress(user.getId(), fileName);
840 // We can't return 204 here since GWT's onSubmitComplete won't fire.
841 response.setContentType("text/html");
842 response.getWriter().print("<pre></pre>");
843 } catch (FileUploadException e) {
844 String error = "Error while uploading file";
845 logger.error(error, e);
846 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
847 } catch (GSSIOException e) {
848 if (uploadedFile != null && uploadedFile.exists())
849 uploadedFile.delete();
850 String error = "Error while uploading file";
852 logger.error(error, e);
854 logger.debug(error, e);
855 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
856 } catch (DuplicateNameException e) {
857 if (uploadedFile != null && uploadedFile.exists())
858 uploadedFile.delete();
859 String error = "The specified file name already exists in this folder";
860 logger.error(error, e);
861 response.sendError(HttpServletResponse.SC_CONFLICT, error);
863 } catch (InsufficientPermissionsException e) {
864 if (uploadedFile != null && uploadedFile.exists())
865 uploadedFile.delete();
866 String error = "You don't have the necessary permissions";
867 logger.error(error, e);
868 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
870 } catch (QuotaExceededException e) {
871 if (uploadedFile != null && uploadedFile.exists())
872 uploadedFile.delete();
873 String error = "Not enough free space available";
874 if (logger.isDebugEnabled())
875 logger.debug(error, e);
876 response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
878 } catch (ObjectNotFoundException e) {
879 if (uploadedFile != null && uploadedFile.exists())
880 uploadedFile.delete();
881 String error = "A specified object was not found";
882 logger.error(error, e);
883 response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
884 } catch (RpcException e) {
885 if (uploadedFile != null && uploadedFile.exists())
886 uploadedFile.delete();
887 String error = "An error occurred while communicating with the service";
888 logger.error(error, e);
889 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
893 private String getBackupFilename(FileHeaderDTO file, String filename){
894 List<FileHeaderDTO> deletedFiles = new ArrayList<FileHeaderDTO>();
896 deletedFiles = getService().getDeletedFiles(file.getOwner().getId());
898 catch(ObjectNotFoundException e){
900 } catch (RpcException e) {
903 List<FileHeaderDTO> filesInSameFolder = new ArrayList<FileHeaderDTO>();
904 for(FileHeaderDTO deleted : deletedFiles)
905 if(deleted.getFolder().getId().equals(file.getFolder().getId()))
906 filesInSameFolder.add(deleted);
908 String filenameToCheck = filename;
909 for(FileHeaderDTO same : filesInSameFolder)
910 if(same.getName().startsWith(filename)){
911 String toCheck=same.getName().substring(filename.length(),same.getName().length());
912 if(toCheck.startsWith(" ")){
915 test = Integer.valueOf(toCheck.replace(" ",""));
917 catch(NumberFormatException e){
918 //do nothing since string is not a number
925 return filename+" "+i;
929 * Move the resource in the specified path to the specified destination.
931 * @param req the HTTP request
932 * @param resp the HTTP response
933 * @param path the path of the resource
934 * @param moveTo the destination of the move procedure
935 * @throws IOException if an input/output error occurs
937 private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
938 User user = getUser(req);
939 User owner = getOwner(req);
940 Object resource = null;
942 resource = getService().getResourceAtPath(owner.getId(), path, true);
943 } catch (ObjectNotFoundException e) {
944 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
946 } catch (RpcException e) {
947 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
951 String destination = null;
952 User destOwner = null;
953 boolean exists = true;
955 destination = getDestinationPath(req, encodePath(moveTo));
956 destination = URLDecoder.decode(destination, "UTF-8");
957 destOwner = getDestinationOwner(req);
958 getService().getResourceAtPath(destOwner.getId(), destination, true);
959 } catch (ObjectNotFoundException e) {
961 } catch (URISyntaxException e) {
962 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
964 } catch (RpcException e) {
965 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
969 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
974 if (resource instanceof FolderDTO) {
975 FolderDTO folder = (FolderDTO) resource;
976 getService().moveFolderToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
978 FileHeaderDTO file = (FileHeaderDTO) resource;
979 getService().moveFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
981 } catch (InsufficientPermissionsException e) {
982 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
983 } catch (ObjectNotFoundException e) {
984 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
985 } catch (RpcException e) {
986 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
987 } catch (DuplicateNameException e) {
988 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
989 } catch (GSSIOException e) {
990 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
991 } catch (QuotaExceededException e) {
992 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
997 * Copy the resource in the specified path to the specified destination.
999 * @param req the HTTP request
1000 * @param resp the HTTP response
1001 * @param path the path of the resource
1002 * @param copyTo the destination of the copy procedure
1003 * @throws IOException if an input/output error occurs
1005 private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1006 User user = getUser(req);
1007 User owner = getOwner(req);
1008 Object resource = null;
1010 resource = getService().getResourceAtPath(owner.getId(), path, true);
1011 } catch (ObjectNotFoundException e) {
1012 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1014 } catch (RpcException e) {
1015 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1019 String destination = null;
1020 User destOwner = null;
1021 boolean exists = true;
1023 destination = getDestinationPath(req, encodePath(copyTo));
1024 destination = URLDecoder.decode(destination, "UTF-8");
1025 destOwner = getDestinationOwner(req);
1026 getService().getResourceAtPath(destOwner.getId(), destination, true);
1027 } catch (ObjectNotFoundException e) {
1029 } catch (URISyntaxException e) {
1030 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1032 } catch (RpcException e) {
1033 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1037 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1042 if (resource instanceof FolderDTO) {
1043 FolderDTO folder = (FolderDTO) resource;
1044 getService().copyFolderStructureToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
1046 FileHeaderDTO file = (FileHeaderDTO) resource;
1047 getService().copyFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
1049 } catch (InsufficientPermissionsException e) {
1050 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1051 } catch (ObjectNotFoundException e) {
1052 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1053 } catch (RpcException e) {
1054 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1055 } catch (DuplicateNameException e) {
1056 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1057 } catch (GSSIOException e) {
1058 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1059 } catch (QuotaExceededException e) {
1060 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1064 private String encodePath(String path) throws UnsupportedEncodingException{
1065 StringTokenizer str = new StringTokenizer(path, "/:", true);
1066 String result = new String();
1067 while(str.hasMoreTokens()){
1068 String token = str.nextToken();
1069 if(!token.equals("/") && !token.equals(":"))
1070 token = URLEncoder.encode(token,"UTF-8");
1071 result = result + token;
1076 * A helper method that extracts the relative resource path,
1077 * after removing the 'files' namespace.
1079 * @param req the HTTP request
1080 * @param path the specified path
1081 * @return the path relative to the root folder
1082 * @throws URISyntaxException
1083 * @throws RpcException in case an error occurs while communicating
1086 private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException {
1087 URI uri = new URI(path);
1088 String dest = uri.getPath();
1089 // Remove the context path from the destination URI.
1090 String contextPath = req.getContextPath();
1091 if (!dest.startsWith(contextPath))
1092 throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1093 dest = dest.substring(contextPath.length());
1094 // Remove the servlet path from the destination URI.
1095 String servletPath = req.getServletPath();
1096 if (!dest.startsWith(servletPath))
1097 throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1098 dest = dest.substring(servletPath.length());
1099 // Strip the username part
1100 if (dest.length() < 2)
1101 throw new URISyntaxException(dest, "No username in the destination URI");
1102 int slash = dest.substring(1).indexOf('/');
1104 throw new URISyntaxException(dest, "No username in the destination URI");
1105 String owner = dest.substring(1, slash + 1);
1107 o = getService().findUser(owner);
1109 throw new URISyntaxException(dest, "User " + owner + " not found");
1111 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1112 dest = dest.substring(slash + 1);
1114 // Chop the resource namespace part
1115 dest = dest.substring(RequestHandler.PATH_FILES.length());
1117 dest = dest.endsWith("/")? dest: dest + '/';
1122 * Move the resource in the specified path to the trash bin.
1124 * @param req the HTTP request
1125 * @param resp the HTTP response
1126 * @param path the path of the resource
1127 * @throws IOException if an input/output error occurs
1129 private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1130 User user = getUser(req);
1131 User owner = getOwner(req);
1132 Object resource = null;
1134 resource = getService().getResourceAtPath(owner.getId(), path, true);
1135 } catch (ObjectNotFoundException e) {
1136 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1138 } catch (RpcException e) {
1139 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1144 if (resource instanceof FolderDTO) {
1145 FolderDTO folder = (FolderDTO) resource;
1146 getService().moveFolderToTrash(user.getId(), folder.getId());
1148 FileHeaderDTO file = (FileHeaderDTO) resource;
1149 getService().moveFileToTrash(user.getId(), file.getId());
1151 } catch (InsufficientPermissionsException e) {
1152 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1153 } catch (ObjectNotFoundException e) {
1154 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1155 } catch (RpcException e) {
1156 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1161 * Restore the resource in the specified path from the trash bin.
1163 * @param req the HTTP request
1164 * @param resp the HTTP response
1165 * @param path the path of the resource
1166 * @throws IOException if an input/output error occurs
1168 private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1169 User user = getUser(req);
1170 User owner = getOwner(req);
1171 Object resource = null;
1173 resource = getService().getResourceAtPath(owner.getId(), path, false);
1174 } catch (ObjectNotFoundException e) {
1175 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1177 } catch (RpcException e) {
1178 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1183 if (resource instanceof FolderDTO) {
1184 FolderDTO folder = (FolderDTO) resource;
1185 getService().removeFolderFromTrash(user.getId(), folder.getId());
1187 FileHeaderDTO file = (FileHeaderDTO) resource;
1188 getService().removeFileFromTrash(user.getId(), file.getId());
1190 } catch (InsufficientPermissionsException e) {
1191 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1192 } catch (ObjectNotFoundException e) {
1193 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1194 } catch (RpcException e) {
1195 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1200 * Update the resource in the specified path.
1202 * @param req the HTTP request
1203 * @param resp the HTTP response
1204 * @param path the path of the resource
1205 * @throws IOException if an input/output error occurs
1207 private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1208 User user = getUser(req);
1209 User owner = getOwner(req);
1210 Object resource = null;
1212 resource = getService().getResourceAtPath(owner.getId(), path, false);
1213 } catch (ObjectNotFoundException e) {
1214 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1216 } catch (RpcException e) {
1217 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1220 //use utf-8 encoding for reading request
1221 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1222 StringBuffer input = new StringBuffer();
1224 JSONObject json = null;
1225 while ((line = reader.readLine()) != null)
1229 json = new JSONObject(input.toString());
1230 if (logger.isDebugEnabled())
1231 logger.debug("JSON update: " + json);
1232 if (resource instanceof FolderDTO) {
1233 FolderDTO folder = (FolderDTO) resource;
1234 String name = json.optString("name");
1235 if (!name.isEmpty()){
1237 name = URLDecoder.decode(name, "UTF-8");
1238 } catch (IllegalArgumentException e) {
1239 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1242 getService().modifyFolder(user.getId(), folder.getId(), name);
1243 FolderDTO folderUpdated = getService().getFolder(user.getId(), folder.getId());
1244 String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1245 String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1246 parentUrl = parentUrl.replaceAll(fpath, "");
1247 if(!parentUrl.endsWith("/"))
1248 parentUrl = parentUrl+"/";
1249 parentUrl = parentUrl+folderUpdated.getOwner().getUsername()+PATH_FILES+folderUpdated.getPath();
1250 resp.getWriter().println(parentUrl);
1253 JSONArray permissions = json.optJSONArray("permissions");
1254 if (permissions != null) {
1255 Set<PermissionDTO> perms = parsePermissions(user, permissions);
1256 getService().setFolderPermissions(user.getId(), folder.getId(), perms);
1259 FileHeaderDTO file = (FileHeaderDTO) resource;
1261 if (json.opt("name") != null)
1262 name = json.optString("name");
1263 JSONArray tagset = json.optJSONArray("tags");
1265 StringBuffer t = new StringBuffer();
1266 if (tagset != null) {
1267 for (int i = 0; i < tagset.length(); i++)
1268 t.append(tagset.getString(i) + ',');
1269 tags = t.toString();
1271 if (name != null || tags != null)
1272 getService().updateFile(user.getId(), file.getId(), name, tags);
1274 JSONArray permissions = json.optJSONArray("permissions");
1275 Set<PermissionDTO> perms = null;
1276 if (permissions != null)
1277 perms = parsePermissions(user, permissions);
1278 Boolean readForAll = null;
1279 if (json.opt("readForAll") != null)
1280 readForAll = json.optBoolean("readForAll");
1281 if (perms != null || readForAll != null)
1282 getService().setFilePermissions(user.getId(), file.getId(), readForAll, perms);
1284 if (json.opt("versioned") != null) {
1285 boolean versioned = json.getBoolean("versioned");
1286 getService().toggleFileVersioning(user.getId(), file.getId(), versioned);
1289 } catch (JSONException e) {
1290 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1291 } catch (InsufficientPermissionsException e) {
1292 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1293 } catch (ObjectNotFoundException e) {
1294 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1295 } catch (DuplicateNameException e) {
1296 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1297 } catch (RpcException e) {
1298 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1303 * Helper method to convert a JSON array of permissions into a set of
1304 * PermissionDTO objects.
1306 * @param user the current user
1307 * @param permissions the JSON array to parse
1308 * @return the parsed set of permissions
1309 * @throws JSONException if there was an error parsing the JSON object
1310 * @throws RpcException if there was an error communicating with the EJB
1311 * @throws ObjectNotFoundException if the user could not be found
1313 private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1314 throws JSONException, RpcException, ObjectNotFoundException {
1315 if (permissions == null)
1317 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1318 for (int i = 0; i < permissions.length(); i++) {
1319 JSONObject j = permissions.getJSONObject(i);
1320 PermissionDTO perm = new PermissionDTO();
1321 perm.setModifyACL(j.optBoolean("modifyACL"));
1322 perm.setRead(j.optBoolean("read"));
1323 perm.setWrite(j.optBoolean("write"));
1324 String permUser = j.optString("user");
1325 if (!permUser.isEmpty()) {
1326 User u = getService().findUser(permUser);
1328 throw new ObjectNotFoundException("User " + permUser + " not found");
1329 perm.setUser(u.getDTO());
1331 String permGroup = j.optString("group");
1332 if (!permGroup.isEmpty()) {
1333 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1336 if (permUser.isEmpty() && permGroup.isEmpty() ||
1337 permUser.isEmpty() && permGroup.isEmpty())
1338 throw new JSONException("A permission must correspond to either a user or a group");
1345 * Creates a new folder with the specified name under the folder in the provided path.
1347 * @param req the HTTP request
1348 * @param resp the HTTP response
1349 * @param path the parent folder path
1350 * @param folderName the name of the new folder
1351 * @throws IOException if an input/output error occurs
1353 private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, String folderName) throws IOException {
1354 if (logger.isDebugEnabled())
1355 logger.debug("Creating folder " + folderName + " in '" + path);
1357 User user = getUser(req);
1358 User owner = getOwner(req);
1359 boolean exists = true;
1361 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1362 } catch (ObjectNotFoundException e) {
1364 } catch (RpcException e) {
1365 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1370 resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1371 ", " + METHOD_HEAD);
1372 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1378 parent = getService().getResourceAtPath(owner.getId(), path, true);
1379 } catch (ObjectNotFoundException e) {
1380 resp.sendError(HttpServletResponse.SC_CONFLICT);
1382 } catch (RpcException e) {
1383 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1387 if (parent instanceof FolderDTO) {
1388 FolderDTO folder = (FolderDTO) parent;
1389 getService().createFolder(user.getId(), folder.getId(), folderName);
1390 String newResource = getContextPath(req, true) + folderName;
1391 resp.setHeader("Location", newResource);
1392 resp.setContentType("text/plain");
1393 PrintWriter out = resp.getWriter();
1394 out.println(newResource);
1396 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1399 } catch (DuplicateNameException e) {
1400 resp.sendError(HttpServletResponse.SC_CONFLICT);
1402 } catch (InsufficientPermissionsException e) {
1403 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1405 } catch (ObjectNotFoundException e) {
1406 resp.sendError(HttpServletResponse.SC_CONFLICT);
1408 } catch (RpcException e) {
1409 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1412 resp.setStatus(HttpServletResponse.SC_CREATED);
1418 * @throws IOException
1419 * @throws FileNotFoundException
1421 void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1422 String path = getInnerPath(req, PATH_FILES);
1424 path = URLDecoder.decode(path, "UTF-8");
1425 } catch (IllegalArgumentException e) {
1426 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1429 if (logger.isDebugEnabled())
1430 logger.debug("Updating resource: " + path);
1432 final User user = getUser(req);
1433 User owner = getOwner(req);
1434 boolean exists = true;
1435 Object resource = null;
1436 FileHeaderDTO file = null;
1438 resource = getService().getResourceAtPath(owner.getId(), path, false);
1439 } catch (ObjectNotFoundException e) {
1441 } catch (RpcException e) {
1442 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1447 if (resource instanceof FileHeaderDTO)
1448 file = (FileHeaderDTO) resource;
1450 resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1453 boolean result = true;
1455 // Temporary content file used to support partial PUT.
1456 File contentFile = null;
1458 Range range = parseContentRange(req, resp);
1460 InputStream resourceInputStream = null;
1462 // Append data specified in ranges to existing content for this
1463 // resource - create a temporary file on the local filesystem to
1464 // perform this operation.
1465 // Assume just one range is specified for now
1466 if (range != null) {
1468 contentFile = executePartialPut(req, range, path);
1469 } catch (RpcException e) {
1470 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1472 } catch (ObjectNotFoundException e) {
1473 resp.sendError(HttpServletResponse.SC_CONFLICT);
1475 } catch (InsufficientPermissionsException e) {
1476 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1479 resourceInputStream = new FileInputStream(contentFile);
1481 resourceInputStream = req.getInputStream();
1484 FolderDTO folder = null;
1485 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1486 if (!(parent instanceof FolderDTO)) {
1487 resp.sendError(HttpServletResponse.SC_CONFLICT);
1490 folder = (FolderDTO) parent;
1491 final String name = getLastElement(path);
1492 final String mimeType = context.getMimeType(name);
1493 File uploadedFile = null;
1495 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1496 } catch (IOException ex) {
1497 throw new GSSIOException(ex, false);
1499 FileHeaderDTO fileDTO = null;
1501 fileDTO = getService().updateFileContents(user.getId(), file.getId(), mimeType, uploadedFile.getCanonicalFile().length(), uploadedFile.getAbsolutePath());
1503 final File uploadedf = uploadedFile;
1504 final FolderDTO parentf = folder;
1505 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1507 public FileHeaderDTO call() throws Exception {
1508 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1513 getService().updateAccounting(owner, new Date(), fileDTO.getFileSize());
1514 getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1515 } catch(ObjectNotFoundException e) {
1517 } catch (RpcException e) {
1518 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1520 } catch (IOException e) {
1521 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1523 } catch (GSSIOException e) {
1524 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1526 } catch (DuplicateNameException e) {
1527 resp.sendError(HttpServletResponse.SC_CONFLICT);
1529 } catch (InsufficientPermissionsException e) {
1530 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1532 } catch (QuotaExceededException e) {
1533 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1535 } catch (Exception e) {
1536 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1542 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1544 resp.setStatus(HttpServletResponse.SC_CREATED);
1546 resp.sendError(HttpServletResponse.SC_CONFLICT);
1550 * Delete a resource.
1552 * @param req The servlet request we are processing
1553 * @param resp The servlet response we are processing
1554 * @throws IOException if the response cannot be sent
1556 void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1557 String path = getInnerPath(req, PATH_FILES);
1558 if (logger.isDebugEnabled())
1559 logger.debug("Deleting resource '" + path);
1560 path = URLDecoder.decode(path, "UTF-8");
1561 User user = getUser(req);
1562 User owner = getOwner(req);
1563 boolean exists = true;
1564 Object object = null;
1566 object = getService().getResourceAtPath(owner.getId(), path, false);
1567 } catch (ObjectNotFoundException e) {
1569 } catch (RpcException e) {
1570 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1575 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1579 FolderDTO folder = null;
1580 FileHeaderDTO file = null;
1581 if (object instanceof FolderDTO)
1582 folder = (FolderDTO) object;
1584 file = (FileHeaderDTO) object;
1588 getService().deleteFile(user.getId(), file.getId());
1589 } catch (InsufficientPermissionsException e) {
1590 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1592 } catch (ObjectNotFoundException e) {
1593 // Although we had already found the object, it was
1594 // probably deleted from another thread.
1595 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1597 } catch (RpcException e) {
1598 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1601 else if (folder != null)
1603 getService().deleteFolder(user.getId(), folder.getId());
1604 } catch (InsufficientPermissionsException e) {
1605 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1607 } catch (ObjectNotFoundException e) {
1608 resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1610 } catch (RpcException e) {
1611 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1614 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1619 * Return an InputStream to a JSON representation of the contents
1620 * of this directory.
1622 * @param user the user that made the request
1623 * @param folder the specified directory
1624 * @return an input stream with the rendered contents
1625 * @throws IOException if the response cannot be sent
1626 * @throws ServletException
1627 * @throws InsufficientPermissionsException if the user does not have
1628 * the necessary privileges to read the directory
1630 private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1631 ServletException, InsufficientPermissionsException {
1632 JSONObject json = new JSONObject();
1634 json.put("name", folder.getName()).
1635 put("owner", folder.getOwner().getUsername()).
1636 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1637 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1638 put("deleted", folder.isDeleted());
1639 if (folder.getAuditInfo().getModifiedBy() != null)
1640 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1641 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1642 if (folder.getParent() != null) {
1643 JSONObject j = new JSONObject();
1644 j.put("uri", getApiRoot() + folder.getParent().getURI());
1645 j.put("name", folder.getParent().getName());
1646 json.put("parent", j);
1648 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1649 for (FolderDTO f: folder.getSubfolders())
1650 if (!f.isDeleted()) {
1651 JSONObject j = new JSONObject();
1652 j.put("name", f.getName()).
1653 put("uri", getApiRoot() + f.getURI());
1656 json.put("folders", subfolders);
1657 List<JSONObject> files = new ArrayList<JSONObject>();
1658 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1659 for (FileHeaderDTO f: fileHeaders) {
1660 JSONObject j = new JSONObject();
1661 j.put("name", f.getName()).
1662 put("owner", f.getOwner().getUsername()).
1663 put("deleted", f.isDeleted()).
1664 put("version", f.getVersion()).
1665 put("content", f.getMimeType()).
1666 put("size", f.getFileSize()).
1667 put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1668 put("path", f.getFolder().getPath()).
1669 put("uri", getApiRoot() + f.getURI());
1670 if (f.getAuditInfo().getModificationDate() != null)
1671 j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1674 json.put("files", files);
1675 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1676 json.put("permissions", renderJson(perms));
1677 } catch (JSONException e) {
1678 throw new ServletException(e);
1679 } catch (ObjectNotFoundException e) {
1680 throw new ServletException(e);
1681 } catch (RpcException e) {
1682 throw new ServletException(e);
1685 // Prepare a writer to a buffered area
1686 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1687 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1688 PrintWriter writer = new PrintWriter(osWriter);
1690 // Return an input stream to the underlying bytes
1691 writer.write(json.toString());
1693 return new ByteArrayInputStream(stream.toByteArray());
1697 * Return a String with a JSON representation of the metadata
1698 * of the specified folder.
1699 * @throws RpcException
1700 * @throws InsufficientPermissionsException
1701 * @throws ObjectNotFoundException
1703 private String renderJsonMetadata(User user, FolderDTO folder)
1704 throws ServletException, InsufficientPermissionsException {
1705 // Check if the user has read permission.
1707 if (!getService().canReadFolder(user.getId(), folder.getId()))
1708 throw new InsufficientPermissionsException();
1709 } catch (ObjectNotFoundException e) {
1710 throw new ServletException(e);
1711 } catch (RpcException e) {
1712 throw new ServletException(e);
1715 JSONObject json = new JSONObject();
1717 json.put("name", folder.getName()).
1718 put("owner", folder.getOwner().getUsername()).
1719 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1720 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1721 put("deleted", folder.isDeleted());
1722 if (folder.getAuditInfo().getModifiedBy() != null)
1723 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1724 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1725 } catch (JSONException e) {
1726 throw new ServletException(e);
1728 return json.toString();
1732 * Return a String with a JSON representation of the metadata
1733 * of the specified file. If an old file body is provided, then
1734 * the metadata of that particular version will be returned.
1736 * @param user the user that made the request
1737 * @param file the specified file header
1738 * @param oldBody the version number
1739 * @return the JSON-encoded file
1740 * @throws ServletException
1741 * @throws InsufficientPermissionsException if the user does not have
1742 * the necessary privileges to read the directory
1744 private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
1745 throws ServletException, InsufficientPermissionsException {
1746 JSONObject json = new JSONObject();
1748 // Need to encode file name in order to properly display it in the web client.
1749 json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
1750 put("owner", file.getOwner().getUsername()).
1751 put("versioned", file.isVersioned()).
1752 put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
1753 put("readForAll", file.isReadForAll()).
1754 put("tags", renderJson(file.getTags())).
1755 put("path", file.getFolder().getPath()).
1756 put("uri", getApiRoot() + file.getURI()).
1757 put("deleted", file.isDeleted());
1758 JSONObject j = new JSONObject();
1759 j.put("uri", getApiRoot() + file.getFolder().getURI()).
1760 put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
1761 json.put("folder", j);
1762 if (oldBody != null)
1763 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
1764 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
1765 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
1766 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
1767 put("content", oldBody.getMimeType()).
1768 put("size", oldBody.getFileSize());
1770 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
1771 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
1772 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
1773 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
1774 put("content", file.getMimeType()).
1775 put("size", file.getFileSize());
1776 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
1777 json.put("permissions", renderJson(perms));
1778 } catch (JSONException e) {
1779 throw new ServletException(e);
1780 } catch (ObjectNotFoundException e) {
1781 throw new ServletException(e);
1782 } catch (RpcException e) {
1783 throw new ServletException(e);
1784 } catch (UnsupportedEncodingException e) {
1785 throw new ServletException(e);
1788 return json.toString();
1792 * Return a String with a JSON representation of the
1793 * specified set of permissions.
1795 * @param permissions the set of permissions
1796 * @return the JSON-encoded object
1797 * @throws JSONException
1798 * @throws UnsupportedEncodingException
1800 private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
1801 JSONArray perms = new JSONArray();
1802 for (PermissionDTO p: permissions) {
1803 JSONObject permission = new JSONObject();
1804 permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
1805 if (p.getUser() != null)
1806 permission.put("user", p.getUser().getUsername());
1807 if (p.getGroup() != null)
1808 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
1809 perms.put(permission);
1815 * Return a String with a JSON representation of the
1816 * specified collection of tags.
1818 * @param tags the collection of tags
1819 * @return the JSON-encoded object
1820 * @throws JSONException
1821 * @throws UnsupportedEncodingException
1823 private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
1824 JSONArray tagArray = new JSONArray();
1825 for (String t: tags)
1826 tagArray.put(URLEncoder.encode(t,"UTF-8"));
1831 * Retrieves the user who owns the destination namespace, for a
1832 * copy or move request.
1834 * @param req the HTTP request
1835 * @return the owner of the namespace
1837 protected User getDestinationOwner(HttpServletRequest req) {
1838 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
1842 * A helper inner class for updating the progress status of a file upload.
1846 public static class StatusProgressListener implements ProgressListener {
1847 private int percentLogged = 0;
1848 private long bytesTransferred = 0;
1850 private long fileSize = -100;
1852 private long tenKBRead = -1;
1854 private Long userId;
1856 private String filename;
1858 private ExternalAPI service;
1860 public StatusProgressListener(ExternalAPI aService) {
1865 * Modify the userId.
1867 * @param aUserId the userId to set
1869 public void setUserId(Long aUserId) {
1874 * Modify the filename.
1876 * @param aFilename the filename to set
1878 public void setFilename(String aFilename) {
1879 filename = aFilename;
1882 public void update(long bytesRead, long contentLength, int items) {
1883 //monitoring per percent of bytes uploaded
1884 bytesTransferred = bytesRead;
1885 if (fileSize != contentLength)
1886 fileSize = contentLength;
1887 int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
1889 if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
1890 if (percent != percentLogged){
1891 percentLogged = percent;
1893 if (userId != null && filename != null)
1894 service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
1895 } catch (ObjectNotFoundException e) {
1896 // Swallow the exception since it is going to be caught
1897 // by previously called methods