2 * Copyright 2008, 2009 Electronic Business Systems Ltd.
4 * This file is part of GSS.
6 * GSS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GSS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GSS. If not, see <http://www.gnu.org/licenses/>.
19 package gr.ebs.gss.server.rest;
21 import static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22 import gr.ebs.gss.client.exceptions.DuplicateNameException;
23 import gr.ebs.gss.client.exceptions.GSSIOException;
24 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
25 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
26 import gr.ebs.gss.client.exceptions.QuotaExceededException;
27 import gr.ebs.gss.client.exceptions.RpcException;
28 import gr.ebs.gss.server.Login;
29 import gr.ebs.gss.server.domain.FileUploadStatus;
30 import gr.ebs.gss.server.domain.User;
31 import gr.ebs.gss.server.domain.dto.FileBodyDTO;
32 import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
33 import gr.ebs.gss.server.domain.dto.FolderDTO;
34 import gr.ebs.gss.server.domain.dto.GroupDTO;
35 import gr.ebs.gss.server.domain.dto.PermissionDTO;
36 import gr.ebs.gss.server.ejb.ExternalAPI;
37 import gr.ebs.gss.server.ejb.TransactionHelper;
38 import gr.ebs.gss.server.webdav.Range;
40 import java.io.BufferedReader;
41 import java.io.ByteArrayInputStream;
42 import java.io.ByteArrayOutputStream;
44 import java.io.FileInputStream;
45 import java.io.FileNotFoundException;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.InputStreamReader;
49 import java.io.OutputStreamWriter;
50 import java.io.PrintWriter;
51 import java.io.UnsupportedEncodingException;
53 import java.net.URISyntaxException;
54 import java.net.URLDecoder;
55 import java.net.URLEncoder;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.Collection;
59 import java.util.Date;
60 import java.util.HashSet;
61 import java.util.List;
63 import java.util.StringTokenizer;
64 import java.util.concurrent.Callable;
66 import javax.servlet.ServletContext;
67 import javax.servlet.ServletException;
68 import javax.servlet.ServletOutputStream;
69 import javax.servlet.http.Cookie;
70 import javax.servlet.http.HttpServletRequest;
71 import javax.servlet.http.HttpServletResponse;
73 import org.apache.commons.codec.binary.Base64;
74 import org.apache.commons.fileupload.FileItemIterator;
75 import org.apache.commons.fileupload.FileItemStream;
76 import org.apache.commons.fileupload.FileUploadException;
77 import org.apache.commons.fileupload.ProgressListener;
78 import org.apache.commons.fileupload.servlet.ServletFileUpload;
79 import org.apache.commons.fileupload.util.Streams;
80 import org.apache.commons.httpclient.util.DateParseException;
81 import org.apache.commons.httpclient.util.DateUtil;
82 import org.apache.commons.logging.Log;
83 import org.apache.commons.logging.LogFactory;
84 import org.json.JSONArray;
85 import org.json.JSONException;
86 import org.json.JSONObject;
90 * A class that handles operations on the 'files' namespace.
94 public class FilesHandler extends RequestHandler {
96 * The request parameter name for fetching a different version.
98 private static final String VERSION_PARAM = "version";
101 * The request attribute containing the owner of the destination URI
102 * in a copy or move request.
104 private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
106 private static final int TRACK_PROGRESS_PERCENT = 5;
109 * The form parameter name that contains the signature in a browser POST upload.
111 private static final String AUTHORIZATION_PARAMETER = "Authorization";
114 * The form parameter name that contains the date in a browser POST upload.
116 private static final String DATE_PARAMETER = "Date";
119 * The request parameter name for making an upload progress request.
121 private static final String PROGRESS_PARAMETER = "progress";
124 * The request parameter name for restoring a previous version of a file.
126 private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
131 private static Log logger = LogFactory.getLog(FilesHandler.class);
134 * The servlet context provided by the call site.
136 private ServletContext context;
139 * @param servletContext
141 public FilesHandler(ServletContext servletContext) {
142 context = servletContext;
145 private void updateAccounting(final User user, final Date date, final long bandwidthDiff) {
147 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
149 public Void call() throws Exception {
150 getService().updateAccounting(user, date, bandwidthDiff);
154 } catch (RuntimeException e) {
156 } catch (Exception e) {
157 // updateAccounting() doesn't throw any checked exceptions
163 * Serve the specified resource, optionally including the data content.
165 * @param req The servlet request we are processing
166 * @param resp The servlet response we are creating
167 * @param content Should the content be included?
169 * @exception IOException if an input/output error occurs
170 * @exception ServletException if a servlet-specified error occurs
171 * @throws RpcException
172 * @throws InsufficientPermissionsException
173 * @throws ObjectNotFoundException
176 protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
177 throws IOException, ServletException {
178 boolean authDeferred = getAuthDeferred(req);
179 String path = getInnerPath(req, PATH_FILES);
183 path = URLDecoder.decode(path, "UTF-8");
184 } catch (IllegalArgumentException e) {
185 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
188 String progress = req.getParameter(PROGRESS_PARAMETER);
190 if (logger.isDebugEnabled())
192 logger.debug("Serving resource '" + path + "' headers and data");
194 logger.debug("Serving resource '" + path + "' headers only");
196 User user = getUser(req);
197 User owner = getOwner(req);
198 if (user == null) user = owner;
199 boolean exists = true;
200 Object resource = null;
201 FileHeaderDTO file = null;
202 FolderDTO folder = null;
204 resource = getService().getResourceAtPath(owner.getId(), path, false);
205 } catch (ObjectNotFoundException e) {
207 } catch (RpcException e) {
208 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
214 // We do not want to leak information if the request
215 // was not authenticated.
216 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
219 // A request for upload progress.
220 if (progress != null && content) {
221 serveProgress(req, resp, progress, user, null);
225 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
229 if (resource instanceof FolderDTO)
230 folder = (FolderDTO) resource;
232 file = (FileHeaderDTO) resource;
234 // Now it's time to perform the deferred authentication check.
235 // Since regular signature checking was already performed,
236 // we need to check the read-all flag or the signature-in-parameters.
238 if (file != null && !file.isReadForAll() && content) {
239 // Check for GET with the signature in the request parameters.
240 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
241 String dateParam = req.getParameter(DATE_PARAMETER);
242 if (auth == null || dateParam == null) {
243 // Check for a valid authentication cookie.
244 if (req.getCookies() != null) {
245 boolean found = false;
246 for (Cookie cookie : req.getCookies())
247 if (Login.AUTH_COOKIE.equals(cookie.getName())) {
248 String cookieauth = cookie.getValue();
249 int sepIndex = cookieauth.indexOf(Login.COOKIE_SEPARATOR);
250 if (sepIndex == -1) {
251 handleAuthFailure(req, resp);
254 String username = URLDecoder.decode(cookieauth.substring(0, sepIndex), "US-ASCII");
257 user = getService().findUser(username);
258 } catch (RpcException e) {
259 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
263 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
266 req.setAttribute(USER_ATTRIBUTE, user);
267 String token = cookieauth.substring(sepIndex + 1);
268 if (user.getAuthToken() == null) {
269 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
272 if (!Arrays.equals(user.getAuthToken(), Base64.decodeBase64(token))) {
273 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
280 handleAuthFailure(req, resp);
284 handleAuthFailure(req, resp);
290 timestamp = DateUtil.parseDate(dateParam).getTime();
291 } catch (DateParseException e) {
292 resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
295 if (!isTimeValid(timestamp)) {
296 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
300 // Fetch the Authorization parameter and find the user specified in it.
301 String[] authParts = auth.split(" ");
302 if (authParts.length != 2) {
303 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
306 String username = authParts[0];
307 String signature = authParts[1];
310 user = getService().findUser(username);
311 } catch (RpcException e) {
312 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
316 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
319 req.setAttribute(USER_ATTRIBUTE, user);
321 // Remove the servlet path from the request URI.
322 String p = req.getRequestURI();
323 String servletPath = req.getContextPath() + req.getServletPath();
324 p = p.substring(servletPath.length());
325 // Validate the signature in the Authorization parameter.
326 String data = req.getMethod() + dateParam + p;
327 if (!isSignatureValid(signature, user, data)) {
328 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
332 } else if (file != null && !file.isReadForAll() || file == null) {
333 // Check for a read-for-all file request.
334 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
338 // If the resource is not a collection, and the resource path
339 // ends with "/" or "\", return NOT FOUND.
341 if (path.endsWith("/") || path.endsWith("\\")) {
342 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
346 // Workaround for IE's broken caching behavior.
348 resp.setHeader("Expires", "-1");
350 // A request for upload progress.
351 if (progress != null && content) {
353 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
356 serveProgress(req, resp, progress, user, file);
360 // Fetch the version to retrieve, if specified.
361 String verStr = req.getParameter(VERSION_PARAM);
363 FileBodyDTO oldBody = null;
364 if (verStr != null && file != null)
366 version = Integer.valueOf(verStr);
367 } catch (NumberFormatException e) {
368 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
373 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
374 } catch (RpcException e) {
375 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
377 } catch (ObjectNotFoundException e) {
378 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
380 } catch (InsufficientPermissionsException e) {
381 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
385 // Check if the conditions specified in the optional If headers are
386 // satisfied. Doing this for folders would require recursive checking
387 // for all of their children, which in turn would defy the purpose of
390 // Checking If headers.
391 if (!checkIfHeaders(req, resp, file, oldBody))
394 else if(!checkIfModifiedSince(req, resp, folder))
397 // Find content type.
398 String contentType = null;
400 contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
401 if (contentType == null) {
402 contentType = context.getMimeType(file.getName());
403 file.setMimeType(contentType);
406 contentType = "application/json;charset=UTF-8";
408 ArrayList ranges = null;
409 long contentLength = -1L;
412 // Parse range specifier.
413 ranges = parseRange(req, resp, file, oldBody);
415 resp.setHeader("ETag", getETag(file, oldBody));
416 // Last-Modified header.
417 String lastModified = oldBody == null ?
418 getLastModifiedHttp(file.getAuditInfo()) :
419 getLastModifiedHttp(oldBody.getAuditInfo());
420 resp.setHeader("Last-Modified", lastModified);
421 // X-GSS-Metadata header.
423 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
424 } catch (InsufficientPermissionsException e) {
425 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
428 // Get content length.
429 contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
430 // Special case for zero length files, which would cause a
431 // (silent) ISE when setting the output buffer size.
432 if (contentLength == 0L)
435 // Set the folder X-GSS-Metadata header.
437 resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
438 } catch (InsufficientPermissionsException e) {
439 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
443 ServletOutputStream ostream = null;
444 PrintWriter writer = null;
448 ostream = resp.getOutputStream();
449 } catch (IllegalStateException e) {
450 // If it fails, we try to get a Writer instead if we're
451 // trying to serve a text file
452 if ( contentType == null
453 || contentType.startsWith("text")
454 || contentType.endsWith("xml") )
455 writer = resp.getWriter();
461 || (ranges == null || ranges.isEmpty())
462 && req.getHeader("Range") == null
464 // Set the appropriate output headers
465 if (contentType != null) {
466 if (logger.isDebugEnabled())
467 logger.debug("contentType='" + contentType + "'");
468 resp.setContentType(contentType);
470 if (file != null && contentLength >= 0) {
471 if (logger.isDebugEnabled())
472 logger.debug("contentLength=" + contentLength);
473 if (contentLength < Integer.MAX_VALUE)
474 resp.setContentLength((int) contentLength);
476 // Set the content-length as String to be able to use a long
477 resp.setHeader("content-length", "" + contentLength);
480 InputStream renderResult = null;
483 // Serve the directory browser
485 renderResult = renderJson(user, folder);
486 } catch (InsufficientPermissionsException e) {
487 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
490 // Copy the input stream to our output stream (if requested)
493 resp.setBufferSize(output);
494 } catch (IllegalStateException e) {
499 if (needsContentDisposition(req))
500 resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
502 resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
504 copy(file, renderResult, ostream, req, oldBody);
506 copy(file, renderResult, writer, req, oldBody);
507 if (file!=null) updateAccounting(owner, new Date(), contentLength);
508 } catch (ObjectNotFoundException e) {
509 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
511 } catch (InsufficientPermissionsException e) {
512 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
514 } catch (RpcException e) {
515 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
520 if (ranges == null || ranges.isEmpty())
522 // Partial content response.
523 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
525 if (ranges.size() == 1) {
526 Range range = (Range) ranges.get(0);
527 resp.addHeader("Content-Range", "bytes "
529 + "-" + range.end + "/"
531 long length = range.end - range.start + 1;
532 if (length < Integer.MAX_VALUE)
533 resp.setContentLength((int) length);
535 // Set the content-length as String to be able to use a long
536 resp.setHeader("content-length", "" + length);
538 if (contentType != null) {
539 if (logger.isDebugEnabled())
540 logger.debug("contentType='" + contentType + "'");
541 resp.setContentType(contentType);
546 resp.setBufferSize(output);
547 } catch (IllegalStateException e) {
552 copy(file, ostream, range, req, oldBody);
554 copy(file, writer, range, req, oldBody);
555 updateAccounting(owner, new Date(), contentLength);
556 } catch (ObjectNotFoundException e) {
557 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
559 } catch (InsufficientPermissionsException e) {
560 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
562 } catch (RpcException e) {
563 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
568 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
571 resp.setBufferSize(output);
572 } catch (IllegalStateException e) {
577 copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
579 copy(file, writer, ranges.iterator(), contentType, req, oldBody);
580 updateAccounting(owner, new Date(), contentLength);
581 } catch (ObjectNotFoundException e) {
582 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
584 } catch (InsufficientPermissionsException e) {
585 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
587 } catch (RpcException e) {
588 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
597 * Handles an authentication failure. If no Authorization or Date request
598 * parameters and no Authorization, Date or X-GSS-Date headers were present,
599 * this is a browser request, so redirect to login and then let the user get
600 * back to the file. Otherwise it's a bogus client request and Forbidden is
603 private void handleAuthFailure(HttpServletRequest req, HttpServletResponse resp) throws IOException {
604 if (req.getParameter(AUTHORIZATION_PARAMETER) == null &&
605 req.getParameter(DATE_PARAMETER) == null &&
606 req.getHeader(AUTHORIZATION_HEADER) == null &&
607 req.getDateHeader(DATE_HEADER) == -1 &&
608 req.getDateHeader(GSS_DATE_HEADER) == -1)
609 resp.sendRedirect(getConfiguration().getString("loginUrl") +
610 "?next=" + req.getRequestURL().toString());
612 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
616 * Return the filename of the specified file properly formatted for
617 * including in the Content-Disposition header.
619 private String getDispositionFilename(FileHeaderDTO file) throws UnsupportedEncodingException {
620 return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
624 * Determines whether the user agent needs the Content-Disposition
625 * header to be set, in order to properly download a file.
627 * @param req the HTTP request
628 * @return true if the Content-Disposition HTTP header must be set
630 private boolean needsContentDisposition(HttpServletRequest req) {
631 /*String agent = req.getHeader("user-agent");
632 if (agent != null && agent.contains("MSIE"))
634 String dl = req.getParameter("dl");
641 * Sends a progress update on the amount of bytes received until now for
642 * a file that the current user is currently uploading.
644 * @param req the HTTP request
645 * @param resp the HTTP response
646 * @param parameter the value for the progress request parameter
647 * @param user the current user
648 * @param file the file being uploaded, or null if the request is about a new file
649 * @throws IOException if an I/O error occurs
651 private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
652 String parameter, User user, FileHeaderDTO file) throws IOException {
653 String filename = file == null ? parameter : file.getName();
655 FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
656 if (status == null) {
657 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
660 JSONObject json = new JSONObject();
661 json.put("bytesUploaded", status.getBytesUploaded()).
662 put("bytesTotal", status.getFileSize());
663 sendJson(req, resp, json.toString());
665 // Workaround for IE's broken caching behavior.
666 resp.setHeader("Expires", "-1");
668 } catch (RpcException e) {
669 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
671 } catch (JSONException e) {
672 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
678 * Server a POST request to create/modify a file or folder.
680 * @param req the HTTP request
681 * @param resp the HTTP response
682 * @exception IOException if an input/output error occurs
684 void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
685 boolean authDeferred = getAuthDeferred(req);
686 if (!authDeferred && req.getParameterMap().size() > 1) {
687 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
690 String path = getInnerPath(req, PATH_FILES);
691 path = path.endsWith("/")? path: path + '/';
693 path = URLDecoder.decode(path, "UTF-8");
694 } catch (IllegalArgumentException e) {
695 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
698 // We only defer authenticating multipart POST requests.
700 if (!ServletFileUpload.isMultipartContent(req)) {
701 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
704 handleMultipart(req, resp, path);
708 String newName = req.getParameter(NEW_FOLDER_PARAMETER);
709 boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
710 boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
711 boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
712 String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
713 String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
714 String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
717 createFolder(req, resp, path, newName);
718 else if (hasUpdateParam)
719 updateResource(req, resp, path);
720 else if (hasTrashParam)
721 trashResource(req, resp, path);
722 else if (hasRestoreParam)
723 restoreResource(req, resp, path);
724 else if (copyTo != null)
725 copyResource(req, resp, path, copyTo);
726 else if (moveTo != null)
727 moveResource(req, resp, path, moveTo);
728 else if (restoreVersion != null)
729 restoreVersion(req, resp, path, restoreVersion);
731 // IE with Gears uses POST for multiple uploads.
732 putResource(req, resp);
736 * Restores a previous version for a file.
738 * @param req the HTTP request
739 * @param resp the HTTP response
740 * @param path the resource path
741 * @param version the version number to restore
742 * @throws IOException if an I/O error occurs
744 private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
745 final User user = getUser(req);
746 User owner = getOwner(req);
747 Object resource = null;
749 resource = getService().getResourceAtPath(owner.getId(), path, true);
750 } catch (ObjectNotFoundException e) {
751 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
753 } catch (RpcException e) {
754 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
757 if (resource instanceof FolderDTO) {
758 resp.sendError(HttpServletResponse.SC_CONFLICT);
763 final FileHeaderDTO file = (FileHeaderDTO) resource;
764 final int oldVersion = Integer.parseInt(version);
766 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
768 public Void call() throws Exception {
769 getService().restoreVersion(user.getId(), file.getId(), oldVersion);
773 } catch (InsufficientPermissionsException e) {
774 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
775 } catch (ObjectNotFoundException e) {
776 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
777 } catch (RpcException e) {
778 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
779 } catch (GSSIOException e) {
780 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
781 } catch (QuotaExceededException e) {
782 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
783 } catch (NumberFormatException e) {
784 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
785 } catch (Exception e) {
786 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
791 * A method for handling multipart POST requests for uploading
792 * files from browser-based JavaScript clients.
794 * @param request the HTTP request
795 * @param response the HTTP response
796 * @param path the resource path
797 * @throws IOException in case an error occurs writing to the
800 private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
801 if (logger.isDebugEnabled())
802 logger.debug("Multipart POST for resource: " + path);
804 User owner = getOwner(request);
805 boolean exists = true;
806 Object resource = null;
807 FileHeaderDTO file = null;
809 resource = getService().getResourceAtPath(owner.getId(), path, false);
810 } catch (ObjectNotFoundException e) {
812 } catch (RpcException e) {
813 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
818 if (resource instanceof FileHeaderDTO) {
819 file = (FileHeaderDTO) resource;
820 if (file.isDeleted()) {
821 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
825 response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
830 String parentPath = null;
832 parentPath = getParentPath(path);
833 parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
834 } catch (ObjectNotFoundException e) {
835 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
837 } catch (RpcException e) {
838 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
841 if (!(parent instanceof FolderDTO)) {
842 response.sendError(HttpServletResponse.SC_CONFLICT);
845 final FolderDTO folder = (FolderDTO) parent;
846 final String fileName = getLastElement(path);
848 FileItemIterator iter;
849 File uploadedFile = null;
851 // Create a new file upload handler.
852 ServletFileUpload upload = new ServletFileUpload();
853 StatusProgressListener progressListener = new StatusProgressListener(getService());
854 upload.setProgressListener(progressListener);
855 iter = upload.getItemIterator(request);
856 String dateParam = null;
858 while (iter.hasNext()) {
859 FileItemStream item = iter.next();
860 String name = item.getFieldName();
861 InputStream stream = item.openStream();
862 if (item.isFormField()) {
863 final String value = Streams.asString(stream);
864 if (name.equals(DATE_PARAMETER))
866 else if (name.equals(AUTHORIZATION_PARAMETER))
869 if (logger.isDebugEnabled())
870 logger.debug(name + ":" + value);
872 // Fetch the timestamp used to guard against replay attacks.
873 if (dateParam == null) {
874 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
880 timestamp = DateUtil.parseDate(dateParam).getTime();
881 } catch (DateParseException e) {
882 response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
885 if (!isTimeValid(timestamp)) {
886 response.sendError(HttpServletResponse.SC_FORBIDDEN);
890 // Fetch the Authorization parameter and find the user specified in it.
892 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
895 String[] authParts = auth.split(" ");
896 if (authParts.length != 2) {
897 response.sendError(HttpServletResponse.SC_FORBIDDEN);
900 String username = authParts[0];
901 String signature = authParts[1];
904 user = getService().findUser(username);
905 } catch (RpcException e) {
906 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
910 response.sendError(HttpServletResponse.SC_FORBIDDEN);
913 request.setAttribute(USER_ATTRIBUTE, user);
915 // Remove the servlet path from the request URI.
916 String p = request.getRequestURI();
917 String servletPath = request.getContextPath() + request.getServletPath();
918 p = p.substring(servletPath.length());
919 // Validate the signature in the Authorization parameter.
920 String data = request.getMethod() + dateParam + p;
921 if (!isSignatureValid(signature, user, data)) {
922 response.sendError(HttpServletResponse.SC_FORBIDDEN);
926 progressListener.setUserId(user.getId());
927 progressListener.setFilename(fileName);
928 final String contentType = item.getContentType();
931 uploadedFile = getService().uploadFile(stream, user.getId());
932 } catch (IOException ex) {
933 throw new GSSIOException(ex, false);
935 FileHeaderDTO fileDTO = null;
936 final File upf = uploadedFile;
937 final FileHeaderDTO f = file;
940 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
942 public FileHeaderDTO call() throws Exception {
943 return getService().createFile(u.getId(), folder.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
947 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
949 public FileHeaderDTO call() throws Exception {
950 return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
953 updateAccounting(owner, new Date(), fileDTO.getFileSize());
954 getService().removeFileUploadProgress(user.getId(), fileName);
957 // We can't return 204 here since GWT's onSubmitComplete won't fire.
958 response.setContentType("text/html");
959 response.getWriter().print("<pre></pre>");
960 } catch (FileUploadException e) {
961 String error = "Error while uploading file";
962 logger.error(error, e);
963 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
964 } catch (GSSIOException e) {
965 if (uploadedFile != null && uploadedFile.exists())
966 uploadedFile.delete();
967 String error = "Error while uploading file";
969 logger.error(error, e);
971 logger.debug(error, e);
972 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
973 } catch (DuplicateNameException e) {
974 if (uploadedFile != null && uploadedFile.exists())
975 uploadedFile.delete();
976 String error = "The specified file name already exists in this folder";
977 logger.error(error, e);
978 response.sendError(HttpServletResponse.SC_CONFLICT, error);
980 } catch (InsufficientPermissionsException e) {
981 if (uploadedFile != null && uploadedFile.exists())
982 uploadedFile.delete();
983 String error = "You don't have the necessary permissions";
984 logger.error(error, e);
985 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
987 } catch (QuotaExceededException e) {
988 if (uploadedFile != null && uploadedFile.exists())
989 uploadedFile.delete();
990 String error = "Not enough free space available";
991 if (logger.isDebugEnabled())
992 logger.debug(error, e);
993 response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
995 } catch (ObjectNotFoundException e) {
996 if (uploadedFile != null && uploadedFile.exists())
997 uploadedFile.delete();
998 String error = "A specified object was not found";
999 logger.error(error, e);
1000 response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
1001 } catch (RpcException e) {
1002 if (uploadedFile != null && uploadedFile.exists())
1003 uploadedFile.delete();
1004 String error = "An error occurred while communicating with the service";
1005 logger.error(error, e);
1006 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1007 } catch (Exception e) {
1008 if (uploadedFile != null && uploadedFile.exists())
1009 uploadedFile.delete();
1010 String error = "An internal server error occurred";
1011 logger.error(error, e);
1012 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1017 * Move the resource in the specified path to the specified destination.
1019 * @param req the HTTP request
1020 * @param resp the HTTP response
1021 * @param path the path of the resource
1022 * @param moveTo the destination of the move procedure
1023 * @throws IOException if an input/output error occurs
1025 private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
1026 final User user = getUser(req);
1027 User owner = getOwner(req);
1028 Object resource = null;
1030 resource = getService().getResourceAtPath(owner.getId(), path, true);
1031 } catch (ObjectNotFoundException e) {
1032 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1034 } catch (RpcException e) {
1035 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1039 String destination = null;
1040 User destOwner = null;
1041 boolean exists = true;
1043 destination = getDestinationPath(req, encodePath(moveTo));
1044 destination = URLDecoder.decode(destination, "UTF-8");
1045 destOwner = getDestinationOwner(req);
1046 getService().getResourceAtPath(destOwner.getId(), destination, true);
1047 } catch (ObjectNotFoundException e) {
1049 } catch (URISyntaxException e) {
1050 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1052 } catch (RpcException e) {
1053 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1057 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1062 final User dOwner = destOwner;
1063 final String dest = destination;
1064 if (resource instanceof FolderDTO) {
1065 final FolderDTO folder = (FolderDTO) resource;
1066 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1068 public Void call() throws Exception {
1069 getService().moveFolderToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1074 final FileHeaderDTO file = (FileHeaderDTO) resource;
1075 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1077 public Void call() throws Exception {
1078 getService().moveFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1084 } catch (InsufficientPermissionsException e) {
1085 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1086 } catch (ObjectNotFoundException e) {
1087 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1088 } catch (RpcException e) {
1089 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1090 } catch (DuplicateNameException e) {
1091 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1092 } catch (GSSIOException e) {
1093 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1094 } catch (QuotaExceededException e) {
1095 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1096 } catch (Exception e) {
1097 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1102 * Copy the resource in the specified path to the specified destination.
1104 * @param req the HTTP request
1105 * @param resp the HTTP response
1106 * @param path the path of the resource
1107 * @param copyTo the destination of the copy procedure
1108 * @throws IOException if an input/output error occurs
1110 private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1111 final User user = getUser(req);
1112 User owner = getOwner(req);
1113 Object resource = null;
1115 resource = getService().getResourceAtPath(owner.getId(), path, true);
1116 } catch (ObjectNotFoundException e) {
1117 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1119 } catch (RpcException e) {
1120 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1124 String destination = null;
1125 User destOwner = null;
1126 boolean exists = true;
1128 String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1129 destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1130 destOwner = getDestinationOwner(req);
1131 getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1132 } catch (ObjectNotFoundException e) {
1134 } catch (URISyntaxException e) {
1135 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1137 } catch (RpcException e) {
1138 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1142 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1147 final User dOwner = destOwner;
1148 final String dest = destination;
1149 if (resource instanceof FolderDTO) {
1150 final FolderDTO folder = (FolderDTO) resource;
1151 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1153 public Void call() throws Exception {
1154 getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1159 final FileHeaderDTO file = (FileHeaderDTO) resource;
1160 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1162 public Void call() throws Exception {
1163 getService().copyFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1168 } catch (InsufficientPermissionsException e) {
1169 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1170 } catch (ObjectNotFoundException e) {
1171 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1172 } catch (RpcException e) {
1173 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1174 } catch (DuplicateNameException e) {
1175 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1176 } catch (GSSIOException e) {
1177 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1178 } catch (QuotaExceededException e) {
1179 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1180 } catch (Exception e) {
1181 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1185 private String encodePath(String path) throws UnsupportedEncodingException{
1186 StringTokenizer str = new StringTokenizer(path, "/:", true);
1187 String result = new String();
1188 while(str.hasMoreTokens()){
1189 String token = str.nextToken();
1190 if(!token.equals("/") && !token.equals(":"))
1191 token = URLEncoder.encode(token,"UTF-8");
1192 result = result + token;
1197 * A helper method that extracts the relative resource path,
1198 * after removing the 'files' namespace.
1199 * The path returned is <i>not</i> URL-decoded.
1201 * @param req the HTTP request
1202 * @param path the specified path
1203 * @return the path relative to the root folder
1204 * @throws URISyntaxException
1205 * @throws RpcException in case an error occurs while communicating
1207 * @throws UnsupportedEncodingException
1209 private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1210 URI uri = new URI(path);
1211 String dest = uri.getRawPath();
1212 // Remove the context path from the destination URI.
1213 String contextPath = req.getContextPath();
1214 if (!dest.startsWith(contextPath))
1215 throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1216 dest = dest.substring(contextPath.length());
1217 // Remove the servlet path from the destination URI.
1218 String servletPath = req.getServletPath();
1219 if (!dest.startsWith(servletPath))
1220 throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1221 dest = dest.substring(servletPath.length());
1222 // Strip the username part
1223 if (dest.length() < 2)
1224 throw new URISyntaxException(dest, "No username in the destination URI");
1225 int slash = dest.substring(1).indexOf('/');
1227 throw new URISyntaxException(dest, "No username in the destination URI");
1228 // Decode the user to get the proper characters (mainly the @)
1229 String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1231 o = getService().findUser(owner);
1233 throw new URISyntaxException(dest, "User " + owner + " not found");
1235 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1236 dest = dest.substring(slash + 1);
1238 // Chop the resource namespace part
1239 dest = dest.substring(RequestHandler.PATH_FILES.length());
1241 dest = dest.endsWith("/")? dest: dest + '/';
1246 * Move the resource in the specified path to the trash bin.
1248 * @param req the HTTP request
1249 * @param resp the HTTP response
1250 * @param path the path of the resource
1251 * @throws IOException if an input/output error occurs
1253 private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1254 final User user = getUser(req);
1255 User owner = getOwner(req);
1256 Object resource = null;
1258 resource = getService().getResourceAtPath(owner.getId(), path, true);
1259 } catch (ObjectNotFoundException e) {
1260 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1262 } catch (RpcException e) {
1263 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1268 if (resource instanceof FolderDTO) {
1269 final FolderDTO folder = (FolderDTO) resource;
1270 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1272 public Void call() throws Exception {
1273 getService().moveFolderToTrash(user.getId(), folder.getId());
1278 final FileHeaderDTO file = (FileHeaderDTO) resource;
1279 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1281 public Void call() throws Exception {
1282 getService().moveFileToTrash(user.getId(), file.getId());
1287 } catch (InsufficientPermissionsException e) {
1288 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1289 } catch (ObjectNotFoundException e) {
1290 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1291 } catch (RpcException e) {
1292 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1293 } catch (Exception e) {
1294 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1299 * Restore the resource in the specified path from the trash bin.
1301 * @param req the HTTP request
1302 * @param resp the HTTP response
1303 * @param path the path of the resource
1304 * @throws IOException if an input/output error occurs
1306 private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1307 final User user = getUser(req);
1308 User owner = getOwner(req);
1309 Object resource = null;
1311 resource = getService().getResourceAtPath(owner.getId(), path, false);
1312 } catch (ObjectNotFoundException e) {
1313 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1315 } catch (RpcException e) {
1316 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1321 if (resource instanceof FolderDTO) {
1322 final FolderDTO folder = (FolderDTO) resource;
1323 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1325 public Void call() throws Exception {
1326 getService().removeFolderFromTrash(user.getId(), folder.getId());
1331 final FileHeaderDTO file = (FileHeaderDTO) resource;
1332 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1334 public Void call() throws Exception {
1335 getService().removeFileFromTrash(user.getId(), file.getId());
1340 } catch (InsufficientPermissionsException e) {
1341 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1342 } catch (ObjectNotFoundException e) {
1343 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1344 } catch (RpcException e) {
1345 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1346 } catch (Exception e) {
1347 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1352 * Update the resource in the specified path.
1354 * @param req the HTTP request
1355 * @param resp the HTTP response
1356 * @param path the path of the resource
1357 * @throws IOException if an input/output error occurs
1359 private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1360 final User user = getUser(req);
1361 User owner = getOwner(req);
1362 Object resource = null;
1364 resource = getService().getResourceAtPath(owner.getId(), path, false);
1365 } catch (ObjectNotFoundException e) {
1366 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1368 } catch (RpcException e) {
1369 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1372 StringBuffer input = new StringBuffer();
1373 JSONObject json = null;
1374 if (req.getContentType().startsWith("application/x-www-form-urlencoded"))
1375 input.append(req.getParameter(RESOURCE_UPDATE_PARAMETER));
1377 // Assume application/json
1378 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1380 while ((line = reader.readLine()) != null)
1385 json = new JSONObject(input.toString());
1386 if (logger.isDebugEnabled())
1387 logger.debug("JSON update: " + json);
1388 if (resource instanceof FolderDTO) {
1389 final FolderDTO folder = (FolderDTO) resource;
1390 String name = json.optString("name");
1391 if (!name.isEmpty())
1393 name = URLDecoder.decode(name, "UTF-8");
1394 } catch (IllegalArgumentException e) {
1395 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1398 JSONArray permissions = json.optJSONArray("permissions");
1399 Set<PermissionDTO> perms = null;
1400 if (permissions != null)
1401 perms = parsePermissions(user, permissions);
1402 if (!name.isEmpty() || permissions != null) {
1403 final String fName = name.isEmpty()? null: name;
1404 final Set<PermissionDTO> fPerms = perms;
1405 FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1407 public FolderDTO call() throws Exception {
1408 return getService().updateFolder(user.getId(), folder.getId(), fName, fPerms);
1412 resp.getWriter().println(getNewUrl(req, folderUpdated));
1415 final FileHeaderDTO file = (FileHeaderDTO) resource;
1417 if (json.opt("name") != null)
1418 name = json.optString("name");
1419 Long modificationDate = null;
1420 if (json.optLong("modificationDate") != 0)
1421 modificationDate = json.optLong("modificationDate");
1422 Boolean versioned = null;
1423 if (json.opt("versioned") != null)
1424 versioned = json.getBoolean("versioned");
1425 JSONArray tagset = json.optJSONArray("tags");
1427 StringBuffer t = new StringBuffer();
1428 if (tagset != null) {
1429 for (int i = 0; i < tagset.length(); i++)
1430 t.append(tagset.getString(i) + ',');
1431 tags = t.toString();
1433 JSONArray permissions = json.optJSONArray("permissions");
1434 Set<PermissionDTO> perms = null;
1435 if (permissions != null)
1436 perms = parsePermissions(user, permissions);
1437 Boolean readForAll = null;
1438 if (json.opt("readForAll") != null)
1439 readForAll = json.optBoolean("readForAll");
1440 if (name != null || tags != null || modificationDate != null
1441 || versioned != null || perms != null
1442 || readForAll != null) {
1443 final String fName = name;
1444 final String fTags = tags;
1445 final Date mDate = modificationDate != null? new Date(modificationDate): null;
1446 final Boolean fVersioned = versioned;
1447 final Boolean fReadForAll = readForAll;
1448 final Set<PermissionDTO> fPerms = perms;
1449 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1451 public Object call() throws Exception {
1452 getService().updateFile(user.getId(), file.getId(),
1453 fName, fTags, mDate, fVersioned,
1454 fReadForAll, fPerms);
1461 } catch (JSONException e) {
1462 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1463 } catch (InsufficientPermissionsException e) {
1464 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1465 } catch (ObjectNotFoundException e) {
1466 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1467 } catch (DuplicateNameException e) {
1468 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1469 } catch (RpcException e) {
1470 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1471 } catch (Exception e) {
1472 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1478 * Returns the new URL of an updated folder.
1480 private String getNewUrl(HttpServletRequest req, FolderDTO folder) throws UnsupportedEncodingException {
1481 String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1482 String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1483 if (parentUrl.indexOf(fpath) != -1)
1484 parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1485 if(!parentUrl.endsWith("/"))
1486 parentUrl = parentUrl+"/";
1487 parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1492 * Helper method to convert a JSON array of permissions into a set of
1493 * PermissionDTO objects.
1495 * @param user the current user
1496 * @param permissions the JSON array to parse
1497 * @return the parsed set of permissions
1498 * @throws JSONException if there was an error parsing the JSON object
1499 * @throws RpcException if there was an error communicating with the EJB
1500 * @throws ObjectNotFoundException if the user could not be found
1501 * @throws UnsupportedEncodingException
1503 private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1504 throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1505 if (permissions == null)
1507 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1508 for (int i = 0; i < permissions.length(); i++) {
1509 JSONObject j = permissions.getJSONObject(i);
1510 PermissionDTO perm = new PermissionDTO();
1511 perm.setModifyACL(j.optBoolean("modifyACL"));
1512 perm.setRead(j.optBoolean("read"));
1513 perm.setWrite(j.optBoolean("write"));
1514 String permUser = j.optString("user");
1515 if (!permUser.isEmpty()) {
1516 User u = getService().findUser(permUser);
1518 throw new ObjectNotFoundException("User " + permUser + " not found");
1519 perm.setUser(u.getDTO());
1521 // 31/8/2009: Add optional groupUri which takes priority if it exists
1522 String permGroupUri = j.optString("groupUri");
1523 String permGroup = j.optString("group");
1524 if (!permGroupUri.isEmpty()) {
1525 String[] names = permGroupUri.split("/");
1526 String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1527 String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1528 User u = getService().findUser(usr);
1530 throw new ObjectNotFoundException("User " + permUser + " not found");
1531 GroupDTO g = getService().getGroup(u.getId(), grp);
1534 else if (!permGroup.isEmpty()) {
1535 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1538 if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1539 throw new JSONException("A permission must correspond to either a user or a group");
1546 * Creates a new folder with the specified name under the folder in the provided path.
1548 * @param req the HTTP request
1549 * @param resp the HTTP response
1550 * @param path the parent folder path
1551 * @param folderName the name of the new folder
1552 * @throws IOException if an input/output error occurs
1554 private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1555 if (logger.isDebugEnabled())
1556 logger.debug("Creating folder " + folderName + " in '" + path);
1558 final User user = getUser(req);
1559 User owner = getOwner(req);
1560 boolean exists = true;
1562 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1563 } catch (ObjectNotFoundException e) {
1565 } catch (RpcException e) {
1566 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1571 resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1572 ", " + METHOD_HEAD);
1573 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1579 parent = getService().getResourceAtPath(owner.getId(), path, true);
1580 } catch (ObjectNotFoundException e) {
1581 resp.sendError(HttpServletResponse.SC_CONFLICT);
1583 } catch (RpcException e) {
1584 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1588 if (parent instanceof FolderDTO) {
1589 final FolderDTO folder = (FolderDTO) parent;
1590 FolderDTO newFolder = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1592 public FolderDTO call() throws Exception {
1593 return getService().createFolder(user.getId(), folder.getId(), folderName);
1597 String newResource = getApiRoot() + newFolder.getURI();
1598 resp.setHeader("Location", newResource);
1599 resp.setContentType("text/plain");
1600 PrintWriter out = resp.getWriter();
1601 out.println(newResource);
1603 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1606 } catch (DuplicateNameException e) {
1607 resp.sendError(HttpServletResponse.SC_CONFLICT);
1609 } catch (InsufficientPermissionsException e) {
1610 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1612 } catch (ObjectNotFoundException e) {
1613 resp.sendError(HttpServletResponse.SC_CONFLICT);
1615 } catch (RpcException e) {
1616 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1618 } catch (Exception e) {
1619 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1622 resp.setStatus(HttpServletResponse.SC_CREATED);
1628 * @throws IOException
1629 * @throws FileNotFoundException
1631 void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1632 String path = getInnerPath(req, PATH_FILES);
1634 path = URLDecoder.decode(path, "UTF-8");
1635 } catch (IllegalArgumentException e) {
1636 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1639 if (logger.isDebugEnabled())
1640 logger.debug("Updating resource: " + path);
1642 final User user = getUser(req);
1643 User owner = getOwner(req);
1644 boolean exists = true;
1645 Object resource = null;
1646 FileHeaderDTO file = null;
1648 resource = getService().getResourceAtPath(owner.getId(), path, false);
1649 } catch (ObjectNotFoundException e) {
1651 } catch (RpcException e) {
1652 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1657 if (resource instanceof FileHeaderDTO)
1658 file = (FileHeaderDTO) resource;
1660 resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1663 boolean result = true;
1665 // Temporary content file used to support partial PUT.
1666 File contentFile = null;
1668 Range range = parseContentRange(req, resp);
1670 InputStream resourceInputStream = null;
1672 // Append data specified in ranges to existing content for this
1673 // resource - create a temporary file on the local filesystem to
1674 // perform this operation.
1675 // Assume just one range is specified for now
1676 if (range != null) {
1678 contentFile = executePartialPut(req, range, path);
1679 } catch (RpcException e) {
1680 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1682 } catch (ObjectNotFoundException e) {
1683 resp.sendError(HttpServletResponse.SC_CONFLICT);
1685 } catch (InsufficientPermissionsException e) {
1686 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1689 resourceInputStream = new FileInputStream(contentFile);
1691 resourceInputStream = req.getInputStream();
1694 FolderDTO folder = null;
1695 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1696 if (!(parent instanceof FolderDTO)) {
1697 resp.sendError(HttpServletResponse.SC_CONFLICT);
1700 folder = (FolderDTO) parent;
1701 final String name = getLastElement(path);
1702 final String mimeType = context.getMimeType(name);
1703 File uploadedFile = null;
1705 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1706 } catch (IOException ex) {
1707 throw new GSSIOException(ex, false);
1709 FileHeaderDTO fileDTO = null;
1710 final File uploadedf = uploadedFile;
1711 final FolderDTO parentf = folder;
1712 final FileHeaderDTO f = file;
1714 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1716 public FileHeaderDTO call() throws Exception {
1717 return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1721 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1723 public FileHeaderDTO call() throws Exception {
1724 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1728 updateAccounting(owner, new Date(), fileDTO.getFileSize());
1729 getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1730 } catch(ObjectNotFoundException e) {
1732 } catch (RpcException e) {
1733 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1735 } catch (IOException e) {
1736 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1738 } catch (GSSIOException e) {
1739 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1741 } catch (DuplicateNameException e) {
1742 resp.sendError(HttpServletResponse.SC_CONFLICT);
1744 } catch (InsufficientPermissionsException e) {
1745 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1747 } catch (QuotaExceededException e) {
1748 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1750 } catch (Exception e) {
1751 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1757 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1759 resp.setStatus(HttpServletResponse.SC_CREATED);
1761 resp.sendError(HttpServletResponse.SC_CONFLICT);
1765 * Delete a resource.
1767 * @param req The servlet request we are processing
1768 * @param resp The servlet response we are processing
1769 * @throws IOException if the response cannot be sent
1771 void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1772 String path = getInnerPath(req, PATH_FILES);
1773 if (logger.isDebugEnabled())
1774 logger.debug("Deleting resource '" + path);
1775 path = URLDecoder.decode(path, "UTF-8");
1776 final User user = getUser(req);
1777 User owner = getOwner(req);
1778 boolean exists = true;
1779 Object object = null;
1781 object = getService().getResourceAtPath(owner.getId(), path, false);
1782 } catch (ObjectNotFoundException e) {
1784 } catch (RpcException e) {
1785 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1790 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1794 FolderDTO folder = null;
1795 FileHeaderDTO file = null;
1796 if (object instanceof FolderDTO)
1797 folder = (FolderDTO) object;
1799 file = (FileHeaderDTO) object;
1803 final FileHeaderDTO f = file;
1804 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1806 public Void call() throws Exception {
1807 getService().deleteFile(user.getId(), f.getId());
1811 } catch (InsufficientPermissionsException e) {
1812 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1814 } catch (ObjectNotFoundException e) {
1815 // Although we had already found the object, it was
1816 // probably deleted from another thread.
1817 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1819 } catch (RpcException e) {
1820 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1822 } catch (Exception e) {
1823 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1826 else if (folder != null)
1828 final FolderDTO fo = folder;
1829 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1831 public Void call() throws Exception {
1832 getService().deleteFolder(user.getId(), fo.getId());
1836 } catch (InsufficientPermissionsException e) {
1837 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1839 } catch (ObjectNotFoundException e) {
1840 resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1842 } catch (RpcException e) {
1843 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1845 } catch (Exception e) {
1846 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1849 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1854 * Return an InputStream to a JSON representation of the contents
1855 * of this directory.
1857 * @param user the user that made the request
1858 * @param folder the specified directory
1859 * @return an input stream with the rendered contents
1860 * @throws IOException if the response cannot be sent
1861 * @throws ServletException
1862 * @throws InsufficientPermissionsException if the user does not have
1863 * the necessary privileges to read the directory
1865 private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1866 ServletException, InsufficientPermissionsException {
1867 JSONObject json = new JSONObject();
1869 json.put("name", folder.getName()).
1870 put("owner", folder.getOwner().getUsername()).
1871 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1872 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1873 put("deleted", folder.isDeleted());
1874 if (folder.getAuditInfo().getModifiedBy() != null)
1875 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1876 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1877 if (folder.getParent() != null) {
1878 JSONObject j = new JSONObject();
1879 j.put("uri", getApiRoot() + folder.getParent().getURI());
1880 j.put("name", folder.getParent().getName());
1881 json.put("parent", j);
1883 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1884 for (FolderDTO f: folder.getSubfolders())
1885 if (!f.isDeleted()) {
1886 JSONObject j = new JSONObject();
1887 j.put("name", f.getName()).
1888 put("uri", getApiRoot() + f.getURI());
1891 json.put("folders", subfolders);
1892 List<JSONObject> files = new ArrayList<JSONObject>();
1893 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1894 for (FileHeaderDTO f: fileHeaders) {
1895 JSONObject j = new JSONObject();
1896 j.put("name", f.getName()).
1897 put("owner", f.getOwner().getUsername()).
1898 put("deleted", f.isDeleted()).
1899 put("version", f.getVersion()).
1900 put("content", f.getMimeType()).
1901 put("size", f.getFileSize()).
1902 put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1903 put("path", f.getFolder().getPath()).
1904 put("uri", getApiRoot() + f.getURI());
1905 if (f.getAuditInfo().getModificationDate() != null)
1906 j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1909 json.put("files", files);
1910 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1911 json.put("permissions", renderJson(perms));
1912 } catch (JSONException e) {
1913 throw new ServletException(e);
1914 } catch (ObjectNotFoundException e) {
1915 throw new ServletException(e);
1916 } catch (RpcException e) {
1917 throw new ServletException(e);
1920 // Prepare a writer to a buffered area
1921 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1922 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1923 PrintWriter writer = new PrintWriter(osWriter);
1925 // Return an input stream to the underlying bytes
1926 writer.write(json.toString());
1928 return new ByteArrayInputStream(stream.toByteArray());
1932 * Return a String with a JSON representation of the metadata
1933 * of the specified folder.
1934 * @throws RpcException
1935 * @throws InsufficientPermissionsException
1936 * @throws ObjectNotFoundException
1938 private String renderJsonMetadata(User user, FolderDTO folder)
1939 throws ServletException, InsufficientPermissionsException {
1940 // Check if the user has read permission.
1942 if (!getService().canReadFolder(user.getId(), folder.getId()))
1943 throw new InsufficientPermissionsException();
1944 } catch (ObjectNotFoundException e) {
1945 throw new ServletException(e);
1946 } catch (RpcException e) {
1947 throw new ServletException(e);
1950 JSONObject json = new JSONObject();
1952 json.put("name", folder.getName()).
1953 put("owner", folder.getOwner().getUsername()).
1954 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1955 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1956 put("deleted", folder.isDeleted());
1957 if (folder.getAuditInfo().getModifiedBy() != null)
1958 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1959 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1960 } catch (JSONException e) {
1961 throw new ServletException(e);
1963 return json.toString();
1967 * Return a String with a JSON representation of the metadata
1968 * of the specified file. If an old file body is provided, then
1969 * the metadata of that particular version will be returned.
1971 * @param user the user that made the request
1972 * @param file the specified file header
1973 * @param oldBody the version number
1974 * @return the JSON-encoded file
1975 * @throws ServletException
1976 * @throws InsufficientPermissionsException if the user does not have
1977 * the necessary privileges to read the directory
1979 private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
1980 throws ServletException, InsufficientPermissionsException {
1981 JSONObject json = new JSONObject();
1983 // Need to encode file name in order to properly display it in the web client.
1984 json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
1985 put("owner", file.getOwner().getUsername()).
1986 put("versioned", file.isVersioned()).
1987 put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
1988 put("readForAll", file.isReadForAll()).
1989 put("tags", renderJson(file.getTags())).
1990 put("path", file.getFolder().getPath()).
1991 put("uri", getApiRoot() + file.getURI()).
1992 put("deleted", file.isDeleted());
1993 JSONObject j = new JSONObject();
1994 j.put("uri", getApiRoot() + file.getFolder().getURI()).
1995 put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
1996 json.put("folder", j);
1997 if (oldBody != null)
1998 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
1999 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
2000 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
2001 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
2002 put("content", oldBody.getMimeType()).
2003 put("size", oldBody.getFileSize());
2005 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
2006 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
2007 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
2008 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
2009 put("content", file.getMimeType()).
2010 put("size", file.getFileSize());
2011 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
2012 json.put("permissions", renderJson(perms));
2013 } catch (JSONException e) {
2014 throw new ServletException(e);
2015 } catch (ObjectNotFoundException e) {
2016 throw new ServletException(e);
2017 } catch (RpcException e) {
2018 throw new ServletException(e);
2019 } catch (UnsupportedEncodingException e) {
2020 throw new ServletException(e);
2023 return json.toString();
2027 * Return a String with a JSON representation of the
2028 * specified set of permissions.
2030 * @param permissions the set of permissions
2031 * @return the JSON-encoded object
2032 * @throws JSONException
2033 * @throws UnsupportedEncodingException
2035 private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
2036 JSONArray perms = new JSONArray();
2037 for (PermissionDTO p: permissions) {
2038 JSONObject permission = new JSONObject();
2039 permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2040 if (p.getUser() != null)
2041 permission.put("user", p.getUser().getUsername());
2042 if (p.getGroup() != null) {
2043 GroupDTO group = p.getGroup();
2044 permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2045 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2047 perms.put(permission);
2053 * Return a String with a JSON representation of the
2054 * specified collection of tags.
2056 * @param tags the collection of tags
2057 * @return the JSON-encoded object
2058 * @throws JSONException
2059 * @throws UnsupportedEncodingException
2061 private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2062 JSONArray tagArray = new JSONArray();
2063 for (String t: tags)
2064 tagArray.put(URLEncoder.encode(t,"UTF-8"));
2069 * Retrieves the user who owns the destination namespace, for a
2070 * copy or move request.
2072 * @param req the HTTP request
2073 * @return the owner of the namespace
2075 protected User getDestinationOwner(HttpServletRequest req) {
2076 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2080 * A helper inner class for updating the progress status of a file upload.
2084 public static class StatusProgressListener implements ProgressListener {
2085 private int percentLogged = 0;
2086 private long bytesTransferred = 0;
2088 private long fileSize = -100;
2090 private Long userId;
2092 private String filename;
2094 private ExternalAPI service;
2096 public StatusProgressListener(ExternalAPI aService) {
2101 * Modify the userId.
2103 * @param aUserId the userId to set
2105 public void setUserId(Long aUserId) {
2110 * Modify the filename.
2112 * @param aFilename the filename to set
2114 public void setFilename(String aFilename) {
2115 filename = aFilename;
2118 public void update(long bytesRead, long contentLength, int items) {
2119 //monitoring per percent of bytes uploaded
2120 bytesTransferred = bytesRead;
2121 if (fileSize != contentLength)
2122 fileSize = contentLength;
2123 int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2125 if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2126 if (percent != percentLogged){
2127 percentLogged = percent;
2129 if (userId != null && filename != null)
2130 service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2131 } catch (ObjectNotFoundException e) {
2132 // Swallow the exception since it is going to be caught
2133 // by previously called methods