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.ejb.TransactionHelper;
36 import gr.ebs.gss.server.webdav.Range;
38 import java.io.BufferedReader;
39 import java.io.ByteArrayInputStream;
40 import java.io.ByteArrayOutputStream;
42 import java.io.FileInputStream;
43 import java.io.FileNotFoundException;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.InputStreamReader;
47 import java.io.OutputStreamWriter;
48 import java.io.PrintWriter;
49 import java.io.UnsupportedEncodingException;
51 import java.net.URISyntaxException;
52 import java.net.URLDecoder;
53 import java.net.URLEncoder;
54 import java.util.ArrayList;
55 import java.util.Collection;
56 import java.util.Date;
57 import java.util.HashSet;
58 import java.util.List;
60 import java.util.StringTokenizer;
61 import java.util.concurrent.Callable;
63 import javax.servlet.ServletContext;
64 import javax.servlet.ServletException;
65 import javax.servlet.ServletOutputStream;
66 import javax.servlet.http.HttpServletRequest;
67 import javax.servlet.http.HttpServletResponse;
69 import org.apache.commons.fileupload.FileItemIterator;
70 import org.apache.commons.fileupload.FileItemStream;
71 import org.apache.commons.fileupload.FileUploadException;
72 import org.apache.commons.fileupload.ProgressListener;
73 import org.apache.commons.fileupload.servlet.ServletFileUpload;
74 import org.apache.commons.fileupload.util.Streams;
75 import org.apache.commons.httpclient.util.DateParseException;
76 import org.apache.commons.httpclient.util.DateUtil;
77 import org.apache.commons.logging.Log;
78 import org.apache.commons.logging.LogFactory;
79 import org.json.JSONArray;
80 import org.json.JSONException;
81 import org.json.JSONObject;
85 * A class that handles operations on the 'files' namespace.
89 public class FilesHandler extends RequestHandler {
91 * The request parameter name for fetching a different version.
93 private static final String VERSION_PARAM = "version";
96 * The request attribute containing the owner of the destination URI
97 * in a copy or move request.
99 private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
101 private static final int TRACK_PROGRESS_PERCENT = 5;
104 * The form parameter name that contains the signature in a browser POST upload.
106 private static final String AUTHORIZATION_PARAMETER = "Authorization";
109 * The form parameter name that contains the date in a browser POST upload.
111 private static final String DATE_PARAMETER = "Date";
114 * The request parameter name for making an upload progress request.
116 private static final String PROGRESS_PARAMETER = "progress";
119 * The request parameter name for restoring a previous version of a file.
121 private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
126 private static Log logger = LogFactory.getLog(FilesHandler.class);
129 * The servlet context provided by the call site.
131 private ServletContext context;
134 * @param servletContext
136 public FilesHandler(ServletContext servletContext) {
137 context = servletContext;
140 private void updateAccounting(final User user, final Date date, final long bandwidthDiff) {
142 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
144 public Void call() throws Exception {
145 getService().updateAccounting(user, date, bandwidthDiff);
149 } catch (RuntimeException e) {
151 } catch (Exception e) {
152 // updateAccounting() doesn't throw any checked exceptions
158 * Serve the specified resource, optionally including the data content.
160 * @param req The servlet request we are processing
161 * @param resp The servlet response we are creating
162 * @param content Should the content be included?
164 * @exception IOException if an input/output error occurs
165 * @exception ServletException if a servlet-specified error occurs
166 * @throws RpcException
167 * @throws InsufficientPermissionsException
168 * @throws ObjectNotFoundException
171 protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
172 throws IOException, ServletException {
173 boolean authDeferred = getAuthDeferred(req);
174 String path = getInnerPath(req, PATH_FILES);
178 path = URLDecoder.decode(path, "UTF-8");
179 } catch (IllegalArgumentException e) {
180 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
183 String progress = req.getParameter(PROGRESS_PARAMETER);
185 if (logger.isDebugEnabled())
187 logger.debug("Serving resource '" + path + "' headers and data");
189 logger.debug("Serving resource '" + path + "' headers only");
191 User user = getUser(req);
192 User owner = getOwner(req);
193 if (user == null) user = owner;
194 boolean exists = true;
195 Object resource = null;
196 FileHeaderDTO file = null;
197 FolderDTO folder = null;
199 resource = getService().getResourceAtPath(owner.getId(), path, false);
200 } catch (ObjectNotFoundException e) {
202 } catch (RpcException e) {
203 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
209 // We do not want to leak information if the request
210 // was not authenticated.
211 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
214 // A request for upload progress.
215 if (progress != null && content) {
216 serveProgress(req, resp, progress, user, null);
220 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
224 if (resource instanceof FolderDTO)
225 folder = (FolderDTO) resource;
227 file = (FileHeaderDTO) resource;
229 // Now it's time to perform the deferred authentication check.
230 // Since regular signature checking was already performed,
231 // we need to check the read-all flag or the signature-in-parameters.
233 if (file != null && !file.isReadForAll() && content) {
234 // Check for GET with the signature in the request parameters.
235 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
236 String dateParam = req.getParameter(DATE_PARAMETER);
237 if (auth == null || dateParam == null) {
238 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
244 timestamp = DateUtil.parseDate(dateParam).getTime();
245 } catch (DateParseException e) {
246 resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
249 if (!isTimeValid(timestamp)) {
250 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
254 // Fetch the Authorization parameter and find the user specified in it.
255 String[] authParts = auth.split(" ");
256 if (authParts.length != 2) {
257 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
260 String username = authParts[0];
261 String signature = authParts[1];
264 user = getService().findUser(username);
265 } catch (RpcException e) {
266 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
270 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
273 req.setAttribute(USER_ATTRIBUTE, user);
275 // Remove the servlet path from the request URI.
276 String p = req.getRequestURI();
277 String servletPath = req.getContextPath() + req.getServletPath();
278 p = p.substring(servletPath.length());
279 // Validate the signature in the Authorization parameter.
280 String data = req.getMethod() + dateParam + p;
281 if (!isSignatureValid(signature, user, data)) {
282 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
285 } else if (file != null && !file.isReadForAll() || file == null) {
286 // Check for a read-for-all file request.
287 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
291 // If the resource is not a collection, and the resource path
292 // ends with "/" or "\", return NOT FOUND.
294 if (path.endsWith("/") || path.endsWith("\\")) {
295 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
299 // Workaround for IE's broken caching behavior.
301 resp.setHeader("Expires", "-1");
303 // A request for upload progress.
304 if (progress != null && content) {
306 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
309 serveProgress(req, resp, progress, user, file);
313 // Fetch the version to retrieve, if specified.
314 String verStr = req.getParameter(VERSION_PARAM);
316 FileBodyDTO oldBody = null;
317 if (verStr != null && file != null)
319 version = Integer.valueOf(verStr);
320 } catch (NumberFormatException e) {
321 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
326 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
327 } catch (RpcException e) {
328 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
330 } catch (ObjectNotFoundException e) {
331 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
333 } catch (InsufficientPermissionsException e) {
334 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
338 // Check if the conditions specified in the optional If headers are
339 // satisfied. Doing this for folders would require recursive checking
340 // for all of their children, which in turn would defy the purpose of
343 // Checking If headers.
344 if (!checkIfHeaders(req, resp, file, oldBody))
347 else if(!checkIfModifiedSince(req, resp, folder))
350 // Find content type.
351 String contentType = null;
353 contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
354 if (contentType == null) {
355 contentType = context.getMimeType(file.getName());
356 file.setMimeType(contentType);
359 contentType = "application/json;charset=UTF-8";
361 ArrayList ranges = null;
362 long contentLength = -1L;
365 // Parse range specifier.
366 ranges = parseRange(req, resp, file, oldBody);
368 resp.setHeader("ETag", getETag(file, oldBody));
369 // Last-Modified header.
370 String lastModified = oldBody == null ?
371 getLastModifiedHttp(file.getAuditInfo()) :
372 getLastModifiedHttp(oldBody.getAuditInfo());
373 resp.setHeader("Last-Modified", lastModified);
374 // X-GSS-Metadata header.
376 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
377 } catch (InsufficientPermissionsException e) {
378 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
381 // Get content length.
382 contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
383 // Special case for zero length files, which would cause a
384 // (silent) ISE when setting the output buffer size.
385 if (contentLength == 0L)
388 // Set the folder X-GSS-Metadata header.
390 resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
391 } catch (InsufficientPermissionsException e) {
392 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
396 ServletOutputStream ostream = null;
397 PrintWriter writer = null;
401 ostream = resp.getOutputStream();
402 } catch (IllegalStateException e) {
403 // If it fails, we try to get a Writer instead if we're
404 // trying to serve a text file
405 if ( contentType == null
406 || contentType.startsWith("text")
407 || contentType.endsWith("xml") )
408 writer = resp.getWriter();
414 || (ranges == null || ranges.isEmpty())
415 && req.getHeader("Range") == null
417 // Set the appropriate output headers
418 if (contentType != null) {
419 if (logger.isDebugEnabled())
420 logger.debug("contentType='" + contentType + "'");
421 resp.setContentType(contentType);
423 if (file != null && contentLength >= 0) {
424 if (logger.isDebugEnabled())
425 logger.debug("contentLength=" + contentLength);
426 if (contentLength < Integer.MAX_VALUE)
427 resp.setContentLength((int) contentLength);
429 // Set the content-length as String to be able to use a long
430 resp.setHeader("content-length", "" + contentLength);
433 InputStream renderResult = null;
436 // Serve the directory browser
438 renderResult = renderJson(user, folder);
439 } catch (InsufficientPermissionsException e) {
440 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
443 // Copy the input stream to our output stream (if requested)
446 resp.setBufferSize(output);
447 } catch (IllegalStateException e) {
452 if (needsContentDisposition(req))
453 resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
455 resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
457 copy(file, renderResult, ostream, req, oldBody);
459 copy(file, renderResult, writer, req, oldBody);
460 if (file!=null) updateAccounting(owner, new Date(), contentLength);
461 } catch (ObjectNotFoundException e) {
462 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
464 } catch (InsufficientPermissionsException e) {
465 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
467 } catch (RpcException e) {
468 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
473 if (ranges == null || ranges.isEmpty())
475 // Partial content response.
476 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
478 if (ranges.size() == 1) {
479 Range range = (Range) ranges.get(0);
480 resp.addHeader("Content-Range", "bytes "
482 + "-" + range.end + "/"
484 long length = range.end - range.start + 1;
485 if (length < Integer.MAX_VALUE)
486 resp.setContentLength((int) length);
488 // Set the content-length as String to be able to use a long
489 resp.setHeader("content-length", "" + length);
491 if (contentType != null) {
492 if (logger.isDebugEnabled())
493 logger.debug("contentType='" + contentType + "'");
494 resp.setContentType(contentType);
499 resp.setBufferSize(output);
500 } catch (IllegalStateException e) {
505 copy(file, ostream, range, req, oldBody);
507 copy(file, writer, range, req, oldBody);
508 updateAccounting(owner, new Date(), contentLength);
509 } catch (ObjectNotFoundException e) {
510 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
512 } catch (InsufficientPermissionsException e) {
513 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
515 } catch (RpcException e) {
516 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
521 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
524 resp.setBufferSize(output);
525 } catch (IllegalStateException e) {
530 copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
532 copy(file, writer, ranges.iterator(), contentType, req, oldBody);
533 updateAccounting(owner, new Date(), contentLength);
534 } catch (ObjectNotFoundException e) {
535 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
537 } catch (InsufficientPermissionsException e) {
538 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
540 } catch (RpcException e) {
541 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
550 * Return the filename of the specified file properly formatted for
551 * including in the Content-Disposition header.
553 private String getDispositionFilename(FileHeaderDTO file) throws UnsupportedEncodingException {
554 return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
558 * Determines whether the user agent needs the Content-Disposition
559 * header to be set, in order to properly download a file.
561 * @param req the HTTP request
562 * @return true if the Content-Disposition HTTP header must be set
564 private boolean needsContentDisposition(HttpServletRequest req) {
565 /*String agent = req.getHeader("user-agent");
566 if (agent != null && agent.contains("MSIE"))
568 String dl = req.getParameter("dl");
575 * Sends a progress update on the amount of bytes received until now for
576 * a file that the current user is currently uploading.
578 * @param req the HTTP request
579 * @param resp the HTTP response
580 * @param parameter the value for the progress request parameter
581 * @param user the current user
582 * @param file the file being uploaded, or null if the request is about a new file
583 * @throws IOException if an I/O error occurs
585 private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
586 String parameter, User user, FileHeaderDTO file) throws IOException {
587 String filename = file == null ? parameter : file.getName();
589 FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
590 if (status == null) {
591 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
594 JSONObject json = new JSONObject();
595 json.put("bytesUploaded", status.getBytesUploaded()).
596 put("bytesTotal", status.getFileSize());
597 sendJson(req, resp, json.toString());
599 // Workaround for IE's broken caching behavior.
600 resp.setHeader("Expires", "-1");
602 } catch (RpcException e) {
603 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
605 } catch (JSONException e) {
606 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
612 * Server a POST request to create/modify a file or folder.
614 * @param req the HTTP request
615 * @param resp the HTTP response
616 * @exception IOException if an input/output error occurs
618 void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
619 boolean authDeferred = getAuthDeferred(req);
620 if (!authDeferred && req.getParameterMap().size() > 1) {
621 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
624 String path = getInnerPath(req, PATH_FILES);
625 path = path.endsWith("/")? path: path + '/';
627 path = URLDecoder.decode(path, "UTF-8");
628 } catch (IllegalArgumentException e) {
629 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
632 // We only defer authenticating multipart POST requests.
634 if (!ServletFileUpload.isMultipartContent(req)) {
635 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
638 handleMultipart(req, resp, path);
642 String newName = req.getParameter(NEW_FOLDER_PARAMETER);
643 boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
644 boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
645 boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
646 String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
647 String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
648 String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
651 createFolder(req, resp, path, newName);
652 else if (hasUpdateParam)
653 updateResource(req, resp, path);
654 else if (hasTrashParam)
655 trashResource(req, resp, path);
656 else if (hasRestoreParam)
657 restoreResource(req, resp, path);
658 else if (copyTo != null)
659 copyResource(req, resp, path, copyTo);
660 else if (moveTo != null)
661 moveResource(req, resp, path, moveTo);
662 else if (restoreVersion != null)
663 restoreVersion(req, resp, path, restoreVersion);
665 // IE with Gears uses POST for multiple uploads.
666 putResource(req, resp);
670 * Restores a previous version for a file.
672 * @param req the HTTP request
673 * @param resp the HTTP response
674 * @param path the resource path
675 * @param version the version number to restore
676 * @throws IOException if an I/O error occurs
678 private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
679 final User user = getUser(req);
680 User owner = getOwner(req);
681 Object resource = null;
683 resource = getService().getResourceAtPath(owner.getId(), path, true);
684 } catch (ObjectNotFoundException e) {
685 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
687 } catch (RpcException e) {
688 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
691 if (resource instanceof FolderDTO) {
692 resp.sendError(HttpServletResponse.SC_CONFLICT);
697 final FileHeaderDTO file = (FileHeaderDTO) resource;
698 final int oldVersion = Integer.parseInt(version);
700 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
702 public Void call() throws Exception {
703 getService().restoreVersion(user.getId(), file.getId(), oldVersion);
707 } catch (InsufficientPermissionsException e) {
708 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
709 } catch (ObjectNotFoundException e) {
710 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
711 } catch (RpcException e) {
712 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
713 } catch (GSSIOException e) {
714 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
715 } catch (QuotaExceededException e) {
716 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
717 } catch (NumberFormatException e) {
718 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
719 } catch (Exception e) {
720 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
725 * A method for handling multipart POST requests for uploading
726 * files from browser-based JavaScript clients.
728 * @param request the HTTP request
729 * @param response the HTTP response
730 * @param path the resource path
731 * @throws IOException in case an error occurs writing to the
734 private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
735 if (logger.isDebugEnabled())
736 logger.debug("Multipart POST for resource: " + path);
738 User owner = getOwner(request);
739 boolean exists = true;
740 Object resource = null;
741 FileHeaderDTO file = null;
743 resource = getService().getResourceAtPath(owner.getId(), path, false);
744 } catch (ObjectNotFoundException e) {
746 } catch (RpcException e) {
747 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
752 if (resource instanceof FileHeaderDTO) {
753 file = (FileHeaderDTO) resource;
754 if (file.isDeleted()) {
755 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
759 response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
764 String parentPath = null;
766 parentPath = getParentPath(path);
767 parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
768 } catch (ObjectNotFoundException e) {
769 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
771 } catch (RpcException e) {
772 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
775 if (!(parent instanceof FolderDTO)) {
776 response.sendError(HttpServletResponse.SC_CONFLICT);
779 final FolderDTO folder = (FolderDTO) parent;
780 final String fileName = getLastElement(path);
782 FileItemIterator iter;
783 File uploadedFile = null;
785 // Create a new file upload handler.
786 ServletFileUpload upload = new ServletFileUpload();
787 StatusProgressListener progressListener = new StatusProgressListener(getService());
788 upload.setProgressListener(progressListener);
789 iter = upload.getItemIterator(request);
790 String dateParam = null;
792 while (iter.hasNext()) {
793 FileItemStream item = iter.next();
794 String name = item.getFieldName();
795 InputStream stream = item.openStream();
796 if (item.isFormField()) {
797 final String value = Streams.asString(stream);
798 if (name.equals(DATE_PARAMETER))
800 else if (name.equals(AUTHORIZATION_PARAMETER))
803 if (logger.isDebugEnabled())
804 logger.debug(name + ":" + value);
806 // Fetch the timestamp used to guard against replay attacks.
807 if (dateParam == null) {
808 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
814 timestamp = DateUtil.parseDate(dateParam).getTime();
815 } catch (DateParseException e) {
816 response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
819 if (!isTimeValid(timestamp)) {
820 response.sendError(HttpServletResponse.SC_FORBIDDEN);
824 // Fetch the Authorization parameter and find the user specified in it.
826 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
829 String[] authParts = auth.split(" ");
830 if (authParts.length != 2) {
831 response.sendError(HttpServletResponse.SC_FORBIDDEN);
834 String username = authParts[0];
835 String signature = authParts[1];
838 user = getService().findUser(username);
839 } catch (RpcException e) {
840 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
844 response.sendError(HttpServletResponse.SC_FORBIDDEN);
847 request.setAttribute(USER_ATTRIBUTE, user);
849 // Remove the servlet path from the request URI.
850 String p = request.getRequestURI();
851 String servletPath = request.getContextPath() + request.getServletPath();
852 p = p.substring(servletPath.length());
853 // Validate the signature in the Authorization parameter.
854 String data = request.getMethod() + dateParam + p;
855 if (!isSignatureValid(signature, user, data)) {
856 response.sendError(HttpServletResponse.SC_FORBIDDEN);
860 progressListener.setUserId(user.getId());
861 progressListener.setFilename(fileName);
862 final String contentType = item.getContentType();
865 uploadedFile = getService().uploadFile(stream, user.getId());
866 } catch (IOException ex) {
867 throw new GSSIOException(ex, false);
869 FileHeaderDTO fileDTO = null;
870 final File upf = uploadedFile;
871 final FileHeaderDTO f = file;
874 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
876 public FileHeaderDTO call() throws Exception {
877 return getService().createFile(u.getId(), folder.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
881 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
883 public FileHeaderDTO call() throws Exception {
884 return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
887 updateAccounting(owner, new Date(), fileDTO.getFileSize());
888 getService().removeFileUploadProgress(user.getId(), fileName);
891 // We can't return 204 here since GWT's onSubmitComplete won't fire.
892 response.setContentType("text/html");
893 response.getWriter().print("<pre></pre>");
894 } catch (FileUploadException e) {
895 String error = "Error while uploading file";
896 logger.error(error, e);
897 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
898 } catch (GSSIOException e) {
899 if (uploadedFile != null && uploadedFile.exists())
900 uploadedFile.delete();
901 String error = "Error while uploading file";
903 logger.error(error, e);
905 logger.debug(error, e);
906 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
907 } catch (DuplicateNameException e) {
908 if (uploadedFile != null && uploadedFile.exists())
909 uploadedFile.delete();
910 String error = "The specified file name already exists in this folder";
911 logger.error(error, e);
912 response.sendError(HttpServletResponse.SC_CONFLICT, error);
914 } catch (InsufficientPermissionsException e) {
915 if (uploadedFile != null && uploadedFile.exists())
916 uploadedFile.delete();
917 String error = "You don't have the necessary permissions";
918 logger.error(error, e);
919 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
921 } catch (QuotaExceededException e) {
922 if (uploadedFile != null && uploadedFile.exists())
923 uploadedFile.delete();
924 String error = "Not enough free space available";
925 if (logger.isDebugEnabled())
926 logger.debug(error, e);
927 response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
929 } catch (ObjectNotFoundException e) {
930 if (uploadedFile != null && uploadedFile.exists())
931 uploadedFile.delete();
932 String error = "A specified object was not found";
933 logger.error(error, e);
934 response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
935 } catch (RpcException e) {
936 if (uploadedFile != null && uploadedFile.exists())
937 uploadedFile.delete();
938 String error = "An error occurred while communicating with the service";
939 logger.error(error, e);
940 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
941 } catch (Exception e) {
942 if (uploadedFile != null && uploadedFile.exists())
943 uploadedFile.delete();
944 String error = "An internal server error occurred";
945 logger.error(error, e);
946 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
951 * Move the resource in the specified path to the specified destination.
953 * @param req the HTTP request
954 * @param resp the HTTP response
955 * @param path the path of the resource
956 * @param moveTo the destination of the move procedure
957 * @throws IOException if an input/output error occurs
959 private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
960 final User user = getUser(req);
961 User owner = getOwner(req);
962 Object resource = null;
964 resource = getService().getResourceAtPath(owner.getId(), path, true);
965 } catch (ObjectNotFoundException e) {
966 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
968 } catch (RpcException e) {
969 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
973 String destination = null;
974 User destOwner = null;
975 boolean exists = true;
977 destination = getDestinationPath(req, encodePath(moveTo));
978 destination = URLDecoder.decode(destination, "UTF-8");
979 destOwner = getDestinationOwner(req);
980 getService().getResourceAtPath(destOwner.getId(), destination, true);
981 } catch (ObjectNotFoundException e) {
983 } catch (URISyntaxException e) {
984 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
986 } catch (RpcException e) {
987 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
991 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
996 final User dOwner = destOwner;
997 final String dest = destination;
998 if (resource instanceof FolderDTO) {
999 final FolderDTO folder = (FolderDTO) resource;
1000 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1002 public Void call() throws Exception {
1003 getService().moveFolderToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1008 final FileHeaderDTO file = (FileHeaderDTO) resource;
1009 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1011 public Void call() throws Exception {
1012 getService().moveFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1018 } catch (InsufficientPermissionsException e) {
1019 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1020 } catch (ObjectNotFoundException e) {
1021 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1022 } catch (RpcException e) {
1023 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1024 } catch (DuplicateNameException e) {
1025 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1026 } catch (GSSIOException e) {
1027 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1028 } catch (QuotaExceededException e) {
1029 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1030 } catch (Exception e) {
1031 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1036 * Copy the resource in the specified path to the specified destination.
1038 * @param req the HTTP request
1039 * @param resp the HTTP response
1040 * @param path the path of the resource
1041 * @param copyTo the destination of the copy procedure
1042 * @throws IOException if an input/output error occurs
1044 private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1045 final User user = getUser(req);
1046 User owner = getOwner(req);
1047 Object resource = null;
1049 resource = getService().getResourceAtPath(owner.getId(), path, true);
1050 } catch (ObjectNotFoundException e) {
1051 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1053 } catch (RpcException e) {
1054 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1058 String destination = null;
1059 User destOwner = null;
1060 boolean exists = true;
1062 String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1063 destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1064 destOwner = getDestinationOwner(req);
1065 getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1066 } catch (ObjectNotFoundException e) {
1068 } catch (URISyntaxException e) {
1069 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1071 } catch (RpcException e) {
1072 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1076 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1081 final User dOwner = destOwner;
1082 final String dest = destination;
1083 if (resource instanceof FolderDTO) {
1084 final FolderDTO folder = (FolderDTO) resource;
1085 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1087 public Void call() throws Exception {
1088 getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1093 final FileHeaderDTO file = (FileHeaderDTO) resource;
1094 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1096 public Void call() throws Exception {
1097 getService().copyFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1102 } catch (InsufficientPermissionsException e) {
1103 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1104 } catch (ObjectNotFoundException e) {
1105 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1106 } catch (RpcException e) {
1107 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1108 } catch (DuplicateNameException e) {
1109 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1110 } catch (GSSIOException e) {
1111 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1112 } catch (QuotaExceededException e) {
1113 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1114 } catch (Exception e) {
1115 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1119 private String encodePath(String path) throws UnsupportedEncodingException{
1120 StringTokenizer str = new StringTokenizer(path, "/:", true);
1121 String result = new String();
1122 while(str.hasMoreTokens()){
1123 String token = str.nextToken();
1124 if(!token.equals("/") && !token.equals(":"))
1125 token = URLEncoder.encode(token,"UTF-8");
1126 result = result + token;
1131 * A helper method that extracts the relative resource path,
1132 * after removing the 'files' namespace.
1133 * The path returned is <i>not</i> URL-decoded.
1135 * @param req the HTTP request
1136 * @param path the specified path
1137 * @return the path relative to the root folder
1138 * @throws URISyntaxException
1139 * @throws RpcException in case an error occurs while communicating
1141 * @throws UnsupportedEncodingException
1143 private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1144 URI uri = new URI(path);
1145 String dest = uri.getRawPath();
1146 // Remove the context path from the destination URI.
1147 String contextPath = req.getContextPath();
1148 if (!dest.startsWith(contextPath))
1149 throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1150 dest = dest.substring(contextPath.length());
1151 // Remove the servlet path from the destination URI.
1152 String servletPath = req.getServletPath();
1153 if (!dest.startsWith(servletPath))
1154 throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1155 dest = dest.substring(servletPath.length());
1156 // Strip the username part
1157 if (dest.length() < 2)
1158 throw new URISyntaxException(dest, "No username in the destination URI");
1159 int slash = dest.substring(1).indexOf('/');
1161 throw new URISyntaxException(dest, "No username in the destination URI");
1162 // Decode the user to get the proper characters (mainly the @)
1163 String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1165 o = getService().findUser(owner);
1167 throw new URISyntaxException(dest, "User " + owner + " not found");
1169 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1170 dest = dest.substring(slash + 1);
1172 // Chop the resource namespace part
1173 dest = dest.substring(RequestHandler.PATH_FILES.length());
1175 dest = dest.endsWith("/")? dest: dest + '/';
1180 * Move the resource in the specified path to the trash bin.
1182 * @param req the HTTP request
1183 * @param resp the HTTP response
1184 * @param path the path of the resource
1185 * @throws IOException if an input/output error occurs
1187 private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1188 final User user = getUser(req);
1189 User owner = getOwner(req);
1190 Object resource = null;
1192 resource = getService().getResourceAtPath(owner.getId(), path, true);
1193 } catch (ObjectNotFoundException e) {
1194 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1196 } catch (RpcException e) {
1197 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1202 if (resource instanceof FolderDTO) {
1203 final FolderDTO folder = (FolderDTO) resource;
1204 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1206 public Void call() throws Exception {
1207 getService().moveFolderToTrash(user.getId(), folder.getId());
1212 final FileHeaderDTO file = (FileHeaderDTO) resource;
1213 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1215 public Void call() throws Exception {
1216 getService().moveFileToTrash(user.getId(), file.getId());
1221 } catch (InsufficientPermissionsException e) {
1222 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1223 } catch (ObjectNotFoundException e) {
1224 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1225 } catch (RpcException e) {
1226 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1227 } catch (Exception e) {
1228 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1233 * Restore the resource in the specified path from the trash bin.
1235 * @param req the HTTP request
1236 * @param resp the HTTP response
1237 * @param path the path of the resource
1238 * @throws IOException if an input/output error occurs
1240 private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1241 final User user = getUser(req);
1242 User owner = getOwner(req);
1243 Object resource = null;
1245 resource = getService().getResourceAtPath(owner.getId(), path, false);
1246 } catch (ObjectNotFoundException e) {
1247 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1249 } catch (RpcException e) {
1250 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1255 if (resource instanceof FolderDTO) {
1256 final FolderDTO folder = (FolderDTO) resource;
1257 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1259 public Void call() throws Exception {
1260 getService().removeFolderFromTrash(user.getId(), folder.getId());
1265 final FileHeaderDTO file = (FileHeaderDTO) resource;
1266 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1268 public Void call() throws Exception {
1269 getService().removeFileFromTrash(user.getId(), file.getId());
1274 } catch (InsufficientPermissionsException e) {
1275 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1276 } catch (ObjectNotFoundException e) {
1277 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1278 } catch (RpcException e) {
1279 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1280 } catch (Exception e) {
1281 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1286 * Update the resource in the specified path.
1288 * @param req the HTTP request
1289 * @param resp the HTTP response
1290 * @param path the path of the resource
1291 * @throws IOException if an input/output error occurs
1293 private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1294 final User user = getUser(req);
1295 User owner = getOwner(req);
1296 Object resource = null;
1298 resource = getService().getResourceAtPath(owner.getId(), path, false);
1299 } catch (ObjectNotFoundException e) {
1300 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1302 } catch (RpcException e) {
1303 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1306 //use utf-8 encoding for reading request
1307 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1308 StringBuffer input = new StringBuffer();
1310 JSONObject json = null;
1311 while ((line = reader.readLine()) != null)
1315 json = new JSONObject(input.toString());
1316 if (logger.isDebugEnabled())
1317 logger.debug("JSON update: " + json);
1318 if (resource instanceof FolderDTO) {
1319 final FolderDTO folder = (FolderDTO) resource;
1320 String name = json.optString("name");
1321 if (!name.isEmpty())
1323 name = URLDecoder.decode(name, "UTF-8");
1324 } catch (IllegalArgumentException e) {
1325 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1328 JSONArray permissions = json.optJSONArray("permissions");
1329 Set<PermissionDTO> perms = null;
1330 if (permissions != null)
1331 perms = parsePermissions(user, permissions);
1332 if (!name.isEmpty() || permissions != null) {
1333 final String fName = name.isEmpty()? null: name;
1334 final Set<PermissionDTO> fPerms = perms;
1335 FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1337 public FolderDTO call() throws Exception {
1338 return getService().updateFolder(user.getId(), folder.getId(), fName, fPerms);
1342 resp.getWriter().println(getNewUrl(req, folderUpdated));
1345 final FileHeaderDTO file = (FileHeaderDTO) resource;
1347 if (json.opt("name") != null)
1348 name = json.optString("name");
1349 Long modificationDate = null;
1350 if (json.optLong("modificationDate") != 0)
1351 modificationDate = json.optLong("modificationDate");
1352 Boolean versioned = null;
1353 if (json.opt("versioned") != null)
1354 versioned = json.getBoolean("versioned");
1355 JSONArray tagset = json.optJSONArray("tags");
1357 StringBuffer t = new StringBuffer();
1358 if (tagset != null) {
1359 for (int i = 0; i < tagset.length(); i++)
1360 t.append(tagset.getString(i) + ',');
1361 tags = t.toString();
1363 JSONArray permissions = json.optJSONArray("permissions");
1364 Set<PermissionDTO> perms = null;
1365 if (permissions != null)
1366 perms = parsePermissions(user, permissions);
1367 Boolean readForAll = null;
1368 if (json.opt("readForAll") != null)
1369 readForAll = json.optBoolean("readForAll");
1370 if (name != null || tags != null || modificationDate != null
1371 || versioned != null || perms != null
1372 || readForAll != null) {
1373 final String fName = name;
1374 final String fTags = tags;
1375 final Date mDate = modificationDate != null? new Date(modificationDate): null;
1376 final Boolean fVersioned = versioned;
1377 final Boolean fReadForAll = readForAll;
1378 final Set<PermissionDTO> fPerms = perms;
1379 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1381 public Object call() throws Exception {
1382 getService().updateFile(user.getId(), file.getId(),
1383 fName, fTags, mDate, fVersioned,
1384 fReadForAll, fPerms);
1391 } catch (JSONException e) {
1392 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1393 } catch (InsufficientPermissionsException e) {
1394 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1395 } catch (ObjectNotFoundException e) {
1396 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1397 } catch (DuplicateNameException e) {
1398 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1399 } catch (RpcException e) {
1400 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1401 } catch (Exception e) {
1402 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1408 * Returns the new URL of an updated folder.
1410 private String getNewUrl(HttpServletRequest req, FolderDTO folder) throws UnsupportedEncodingException {
1411 String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1412 String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1413 if (parentUrl.indexOf(fpath) != -1)
1414 parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1415 if(!parentUrl.endsWith("/"))
1416 parentUrl = parentUrl+"/";
1417 parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1422 * Helper method to convert a JSON array of permissions into a set of
1423 * PermissionDTO objects.
1425 * @param user the current user
1426 * @param permissions the JSON array to parse
1427 * @return the parsed set of permissions
1428 * @throws JSONException if there was an error parsing the JSON object
1429 * @throws RpcException if there was an error communicating with the EJB
1430 * @throws ObjectNotFoundException if the user could not be found
1431 * @throws UnsupportedEncodingException
1433 private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1434 throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1435 if (permissions == null)
1437 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1438 for (int i = 0; i < permissions.length(); i++) {
1439 JSONObject j = permissions.getJSONObject(i);
1440 PermissionDTO perm = new PermissionDTO();
1441 perm.setModifyACL(j.optBoolean("modifyACL"));
1442 perm.setRead(j.optBoolean("read"));
1443 perm.setWrite(j.optBoolean("write"));
1444 String permUser = j.optString("user");
1445 if (!permUser.isEmpty()) {
1446 User u = getService().findUser(permUser);
1448 throw new ObjectNotFoundException("User " + permUser + " not found");
1449 perm.setUser(u.getDTO());
1451 // 31/8/2009: Add optional groupUri which takes priority if it exists
1452 String permGroupUri = j.optString("groupUri");
1453 String permGroup = j.optString("group");
1454 if (!permGroupUri.isEmpty()) {
1455 String[] names = permGroupUri.split("/");
1456 String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1457 String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1458 User u = getService().findUser(usr);
1460 throw new ObjectNotFoundException("User " + permUser + " not found");
1461 GroupDTO g = getService().getGroup(u.getId(), grp);
1464 else if (!permGroup.isEmpty()) {
1465 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1468 if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1469 throw new JSONException("A permission must correspond to either a user or a group");
1476 * Creates a new folder with the specified name under the folder in the provided path.
1478 * @param req the HTTP request
1479 * @param resp the HTTP response
1480 * @param path the parent folder path
1481 * @param folderName the name of the new folder
1482 * @throws IOException if an input/output error occurs
1484 private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1485 if (logger.isDebugEnabled())
1486 logger.debug("Creating folder " + folderName + " in '" + path);
1488 final User user = getUser(req);
1489 User owner = getOwner(req);
1490 boolean exists = true;
1492 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1493 } catch (ObjectNotFoundException e) {
1495 } catch (RpcException e) {
1496 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1501 resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1502 ", " + METHOD_HEAD);
1503 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1509 parent = getService().getResourceAtPath(owner.getId(), path, true);
1510 } catch (ObjectNotFoundException e) {
1511 resp.sendError(HttpServletResponse.SC_CONFLICT);
1513 } catch (RpcException e) {
1514 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1518 if (parent instanceof FolderDTO) {
1519 final FolderDTO folder = (FolderDTO) parent;
1520 FolderDTO newFolder = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1522 public FolderDTO call() throws Exception {
1523 return getService().createFolder(user.getId(), folder.getId(), folderName);
1527 String newResource = getApiRoot() + newFolder.getURI();
1528 resp.setHeader("Location", newResource);
1529 resp.setContentType("text/plain");
1530 PrintWriter out = resp.getWriter();
1531 out.println(newResource);
1533 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1536 } catch (DuplicateNameException e) {
1537 resp.sendError(HttpServletResponse.SC_CONFLICT);
1539 } catch (InsufficientPermissionsException e) {
1540 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1542 } catch (ObjectNotFoundException e) {
1543 resp.sendError(HttpServletResponse.SC_CONFLICT);
1545 } catch (RpcException e) {
1546 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1548 } catch (Exception e) {
1549 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1552 resp.setStatus(HttpServletResponse.SC_CREATED);
1558 * @throws IOException
1559 * @throws FileNotFoundException
1561 void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1562 String path = getInnerPath(req, PATH_FILES);
1564 path = URLDecoder.decode(path, "UTF-8");
1565 } catch (IllegalArgumentException e) {
1566 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1569 if (logger.isDebugEnabled())
1570 logger.debug("Updating resource: " + path);
1572 final User user = getUser(req);
1573 User owner = getOwner(req);
1574 boolean exists = true;
1575 Object resource = null;
1576 FileHeaderDTO file = null;
1578 resource = getService().getResourceAtPath(owner.getId(), path, false);
1579 } catch (ObjectNotFoundException e) {
1581 } catch (RpcException e) {
1582 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1587 if (resource instanceof FileHeaderDTO)
1588 file = (FileHeaderDTO) resource;
1590 resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1593 boolean result = true;
1595 // Temporary content file used to support partial PUT.
1596 File contentFile = null;
1598 Range range = parseContentRange(req, resp);
1600 InputStream resourceInputStream = null;
1602 // Append data specified in ranges to existing content for this
1603 // resource - create a temporary file on the local filesystem to
1604 // perform this operation.
1605 // Assume just one range is specified for now
1606 if (range != null) {
1608 contentFile = executePartialPut(req, range, path);
1609 } catch (RpcException e) {
1610 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1612 } catch (ObjectNotFoundException e) {
1613 resp.sendError(HttpServletResponse.SC_CONFLICT);
1615 } catch (InsufficientPermissionsException e) {
1616 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1619 resourceInputStream = new FileInputStream(contentFile);
1621 resourceInputStream = req.getInputStream();
1624 FolderDTO folder = null;
1625 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1626 if (!(parent instanceof FolderDTO)) {
1627 resp.sendError(HttpServletResponse.SC_CONFLICT);
1630 folder = (FolderDTO) parent;
1631 final String name = getLastElement(path);
1632 final String mimeType = context.getMimeType(name);
1633 File uploadedFile = null;
1635 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1636 } catch (IOException ex) {
1637 throw new GSSIOException(ex, false);
1639 FileHeaderDTO fileDTO = null;
1640 final File uploadedf = uploadedFile;
1641 final FolderDTO parentf = folder;
1642 final FileHeaderDTO f = file;
1644 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1646 public FileHeaderDTO call() throws Exception {
1647 return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1651 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1653 public FileHeaderDTO call() throws Exception {
1654 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1658 updateAccounting(owner, new Date(), fileDTO.getFileSize());
1659 getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1660 } catch(ObjectNotFoundException e) {
1662 } catch (RpcException e) {
1663 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1665 } catch (IOException e) {
1666 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1668 } catch (GSSIOException e) {
1669 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1671 } catch (DuplicateNameException e) {
1672 resp.sendError(HttpServletResponse.SC_CONFLICT);
1674 } catch (InsufficientPermissionsException e) {
1675 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1677 } catch (QuotaExceededException e) {
1678 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1680 } catch (Exception e) {
1681 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1687 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1689 resp.setStatus(HttpServletResponse.SC_CREATED);
1691 resp.sendError(HttpServletResponse.SC_CONFLICT);
1695 * Delete a resource.
1697 * @param req The servlet request we are processing
1698 * @param resp The servlet response we are processing
1699 * @throws IOException if the response cannot be sent
1701 void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1702 String path = getInnerPath(req, PATH_FILES);
1703 if (logger.isDebugEnabled())
1704 logger.debug("Deleting resource '" + path);
1705 path = URLDecoder.decode(path, "UTF-8");
1706 final User user = getUser(req);
1707 User owner = getOwner(req);
1708 boolean exists = true;
1709 Object object = null;
1711 object = getService().getResourceAtPath(owner.getId(), path, false);
1712 } catch (ObjectNotFoundException e) {
1714 } catch (RpcException e) {
1715 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1720 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1724 FolderDTO folder = null;
1725 FileHeaderDTO file = null;
1726 if (object instanceof FolderDTO)
1727 folder = (FolderDTO) object;
1729 file = (FileHeaderDTO) object;
1733 final FileHeaderDTO f = file;
1734 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1736 public Void call() throws Exception {
1737 getService().deleteFile(user.getId(), f.getId());
1741 } catch (InsufficientPermissionsException e) {
1742 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1744 } catch (ObjectNotFoundException e) {
1745 // Although we had already found the object, it was
1746 // probably deleted from another thread.
1747 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1749 } catch (RpcException e) {
1750 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1752 } catch (Exception e) {
1753 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1756 else if (folder != null)
1758 final FolderDTO fo = folder;
1759 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1761 public Void call() throws Exception {
1762 getService().deleteFolder(user.getId(), fo.getId());
1766 } catch (InsufficientPermissionsException e) {
1767 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1769 } catch (ObjectNotFoundException e) {
1770 resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1772 } catch (RpcException e) {
1773 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1775 } catch (Exception e) {
1776 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1779 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1784 * Return an InputStream to a JSON representation of the contents
1785 * of this directory.
1787 * @param user the user that made the request
1788 * @param folder the specified directory
1789 * @return an input stream with the rendered contents
1790 * @throws IOException if the response cannot be sent
1791 * @throws ServletException
1792 * @throws InsufficientPermissionsException if the user does not have
1793 * the necessary privileges to read the directory
1795 private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1796 ServletException, InsufficientPermissionsException {
1797 JSONObject json = new JSONObject();
1799 json.put("name", folder.getName()).
1800 put("owner", folder.getOwner().getUsername()).
1801 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1802 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1803 put("deleted", folder.isDeleted());
1804 if (folder.getAuditInfo().getModifiedBy() != null)
1805 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1806 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1807 if (folder.getParent() != null) {
1808 JSONObject j = new JSONObject();
1809 j.put("uri", getApiRoot() + folder.getParent().getURI());
1810 j.put("name", folder.getParent().getName());
1811 json.put("parent", j);
1813 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1814 for (FolderDTO f: folder.getSubfolders())
1815 if (!f.isDeleted()) {
1816 JSONObject j = new JSONObject();
1817 j.put("name", f.getName()).
1818 put("uri", getApiRoot() + f.getURI());
1821 json.put("folders", subfolders);
1822 List<JSONObject> files = new ArrayList<JSONObject>();
1823 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1824 for (FileHeaderDTO f: fileHeaders) {
1825 JSONObject j = new JSONObject();
1826 j.put("name", f.getName()).
1827 put("owner", f.getOwner().getUsername()).
1828 put("deleted", f.isDeleted()).
1829 put("version", f.getVersion()).
1830 put("content", f.getMimeType()).
1831 put("size", f.getFileSize()).
1832 put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1833 put("path", f.getFolder().getPath()).
1834 put("uri", getApiRoot() + f.getURI());
1835 if (f.getAuditInfo().getModificationDate() != null)
1836 j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1839 json.put("files", files);
1840 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1841 json.put("permissions", renderJson(perms));
1842 } catch (JSONException e) {
1843 throw new ServletException(e);
1844 } catch (ObjectNotFoundException e) {
1845 throw new ServletException(e);
1846 } catch (RpcException e) {
1847 throw new ServletException(e);
1850 // Prepare a writer to a buffered area
1851 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1852 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1853 PrintWriter writer = new PrintWriter(osWriter);
1855 // Return an input stream to the underlying bytes
1856 writer.write(json.toString());
1858 return new ByteArrayInputStream(stream.toByteArray());
1862 * Return a String with a JSON representation of the metadata
1863 * of the specified folder.
1864 * @throws RpcException
1865 * @throws InsufficientPermissionsException
1866 * @throws ObjectNotFoundException
1868 private String renderJsonMetadata(User user, FolderDTO folder)
1869 throws ServletException, InsufficientPermissionsException {
1870 // Check if the user has read permission.
1872 if (!getService().canReadFolder(user.getId(), folder.getId()))
1873 throw new InsufficientPermissionsException();
1874 } catch (ObjectNotFoundException e) {
1875 throw new ServletException(e);
1876 } catch (RpcException e) {
1877 throw new ServletException(e);
1880 JSONObject json = new JSONObject();
1882 json.put("name", folder.getName()).
1883 put("owner", folder.getOwner().getUsername()).
1884 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1885 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1886 put("deleted", folder.isDeleted());
1887 if (folder.getAuditInfo().getModifiedBy() != null)
1888 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1889 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1890 } catch (JSONException e) {
1891 throw new ServletException(e);
1893 return json.toString();
1897 * Return a String with a JSON representation of the metadata
1898 * of the specified file. If an old file body is provided, then
1899 * the metadata of that particular version will be returned.
1901 * @param user the user that made the request
1902 * @param file the specified file header
1903 * @param oldBody the version number
1904 * @return the JSON-encoded file
1905 * @throws ServletException
1906 * @throws InsufficientPermissionsException if the user does not have
1907 * the necessary privileges to read the directory
1909 private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
1910 throws ServletException, InsufficientPermissionsException {
1911 JSONObject json = new JSONObject();
1913 // Need to encode file name in order to properly display it in the web client.
1914 json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
1915 put("owner", file.getOwner().getUsername()).
1916 put("versioned", file.isVersioned()).
1917 put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
1918 put("readForAll", file.isReadForAll()).
1919 put("tags", renderJson(file.getTags())).
1920 put("path", file.getFolder().getPath()).
1921 put("uri", getApiRoot() + file.getURI()).
1922 put("deleted", file.isDeleted());
1923 JSONObject j = new JSONObject();
1924 j.put("uri", getApiRoot() + file.getFolder().getURI()).
1925 put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
1926 json.put("folder", j);
1927 if (oldBody != null)
1928 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
1929 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
1930 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
1931 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
1932 put("content", oldBody.getMimeType()).
1933 put("size", oldBody.getFileSize());
1935 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
1936 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
1937 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
1938 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
1939 put("content", file.getMimeType()).
1940 put("size", file.getFileSize());
1941 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
1942 json.put("permissions", renderJson(perms));
1943 } catch (JSONException e) {
1944 throw new ServletException(e);
1945 } catch (ObjectNotFoundException e) {
1946 throw new ServletException(e);
1947 } catch (RpcException e) {
1948 throw new ServletException(e);
1949 } catch (UnsupportedEncodingException e) {
1950 throw new ServletException(e);
1953 return json.toString();
1957 * Return a String with a JSON representation of the
1958 * specified set of permissions.
1960 * @param permissions the set of permissions
1961 * @return the JSON-encoded object
1962 * @throws JSONException
1963 * @throws UnsupportedEncodingException
1965 private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
1966 JSONArray perms = new JSONArray();
1967 for (PermissionDTO p: permissions) {
1968 JSONObject permission = new JSONObject();
1969 permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
1970 if (p.getUser() != null)
1971 permission.put("user", p.getUser().getUsername());
1972 if (p.getGroup() != null) {
1973 GroupDTO group = p.getGroup();
1974 permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
1975 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
1977 perms.put(permission);
1983 * Return a String with a JSON representation of the
1984 * specified collection of tags.
1986 * @param tags the collection of tags
1987 * @return the JSON-encoded object
1988 * @throws JSONException
1989 * @throws UnsupportedEncodingException
1991 private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
1992 JSONArray tagArray = new JSONArray();
1993 for (String t: tags)
1994 tagArray.put(URLEncoder.encode(t,"UTF-8"));
1999 * Retrieves the user who owns the destination namespace, for a
2000 * copy or move request.
2002 * @param req the HTTP request
2003 * @return the owner of the namespace
2005 protected User getDestinationOwner(HttpServletRequest req) {
2006 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2010 * A helper inner class for updating the progress status of a file upload.
2014 public static class StatusProgressListener implements ProgressListener {
2015 private int percentLogged = 0;
2016 private long bytesTransferred = 0;
2018 private long fileSize = -100;
2020 private Long userId;
2022 private String filename;
2024 private ExternalAPI service;
2026 public StatusProgressListener(ExternalAPI aService) {
2031 * Modify the userId.
2033 * @param aUserId the userId to set
2035 public void setUserId(Long aUserId) {
2040 * Modify the filename.
2042 * @param aFilename the filename to set
2044 public void setFilename(String aFilename) {
2045 filename = aFilename;
2048 public void update(long bytesRead, long contentLength, int items) {
2049 //monitoring per percent of bytes uploaded
2050 bytesTransferred = bytesRead;
2051 if (fileSize != contentLength)
2052 fileSize = contentLength;
2053 int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2055 if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2056 if (percent != percentLogged){
2057 percentLogged = percent;
2059 if (userId != null && filename != null)
2060 service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2061 } catch (ObjectNotFoundException e) {
2062 // Swallow the exception since it is going to be caught
2063 // by previously called methods