Fix more problems with international/unusual characters:
[pithos] / gss / src / gr / ebs / gss / server / rest / FilesHandler.java
1 /*
2  * Copyright 2008, 2009 Electronic Business Systems Ltd.
3  *
4  * This file is part of GSS.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19 package gr.ebs.gss.server.rest;
20
21 import gr.ebs.gss.client.exceptions.DuplicateNameException;
22 import gr.ebs.gss.client.exceptions.GSSIOException;
23 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
24 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
25 import gr.ebs.gss.client.exceptions.QuotaExceededException;
26 import gr.ebs.gss.client.exceptions.RpcException;
27 import gr.ebs.gss.server.domain.FileUploadStatus;
28 import gr.ebs.gss.server.domain.User;
29 import gr.ebs.gss.server.domain.dto.FileBodyDTO;
30 import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
31 import gr.ebs.gss.server.domain.dto.FolderDTO;
32 import gr.ebs.gss.server.domain.dto.GroupDTO;
33 import gr.ebs.gss.server.domain.dto.PermissionDTO;
34 import gr.ebs.gss.server.ejb.ExternalAPI;
35 import gr.ebs.gss.server.webdav.Range;
36
37 import java.io.BufferedReader;
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.InputStreamReader;
46 import java.io.OutputStreamWriter;
47 import java.io.PrintWriter;
48 import java.io.UnsupportedEncodingException;
49 import java.net.URI;
50 import java.net.URISyntaxException;
51 import java.net.URLDecoder;
52 import java.net.URLEncoder;
53 import java.util.ArrayList;
54 import java.util.Collection;
55 import java.util.Date;
56 import java.util.HashSet;
57 import java.util.List;
58 import java.util.Set;
59 import java.util.StringTokenizer;
60
61 import javax.servlet.ServletContext;
62 import javax.servlet.ServletException;
63 import javax.servlet.ServletOutputStream;
64 import javax.servlet.http.HttpServletRequest;
65 import javax.servlet.http.HttpServletResponse;
66
67 import org.apache.commons.fileupload.FileItemIterator;
68 import org.apache.commons.fileupload.FileItemStream;
69 import org.apache.commons.fileupload.FileUploadException;
70 import org.apache.commons.fileupload.ProgressListener;
71 import org.apache.commons.fileupload.servlet.ServletFileUpload;
72 import org.apache.commons.fileupload.util.Streams;
73 import org.apache.commons.httpclient.util.DateParseException;
74 import org.apache.commons.httpclient.util.DateUtil;
75 import org.apache.commons.logging.Log;
76 import org.apache.commons.logging.LogFactory;
77 import org.json.JSONArray;
78 import org.json.JSONException;
79 import org.json.JSONObject;
80
81
82 /**
83  * A class that handles operations on the 'files' namespace.
84  *
85  * @author past
86  */
87 public class FilesHandler extends RequestHandler {
88         /**
89          * The request parameter name for fetching a different version.
90          */
91         private static final String VERSION_PARAM = "version";
92
93         /**
94          * The request attribute containing the owner of the destination URI
95          * in a copy or move request.
96          */
97         private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
98
99         private static final int TRACK_PROGRESS_PERCENT = 5;
100
101         /**
102          * The form parameter name that contains the signature in a browser POST upload.
103          */
104         private static final String AUTHORIZATION_PARAMETER = "Authorization";
105
106         /**
107          * The form parameter name that contains the date in a browser POST upload.
108          */
109         private static final String DATE_PARAMETER = "Date";
110
111         /**
112          * The request parameter name for making an upload progress request.
113          */
114         private static final String PROGRESS_PARAMETER = "progress";
115
116         /**
117          * The request parameter name for restoring a previous version of a file.
118          */
119         private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
120
121         /**
122          * The logger.
123          */
124         private static Log logger = LogFactory.getLog(FilesHandler.class);
125
126         /**
127          * The servlet context provided by the call site.
128          */
129         private ServletContext context;
130
131         /**
132          * @param servletContext
133          */
134         public FilesHandler(ServletContext servletContext) {
135                 context = servletContext;
136         }
137
138         /**
139      * Serve the specified resource, optionally including the data content.
140      *
141      * @param req The servlet request we are processing
142      * @param resp The servlet response we are creating
143      * @param content Should the content be included?
144      *
145      * @exception IOException if an input/output error occurs
146      * @exception ServletException if a servlet-specified error occurs
147      * @throws RpcException
148      * @throws InsufficientPermissionsException
149      * @throws ObjectNotFoundException
150      */
151         @Override
152         protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
153                 throws IOException, ServletException {
154                 boolean authDeferred = getAuthDeferred(req);
155         String path = getInnerPath(req, PATH_FILES);
156                 if (path.equals(""))
157                         path = "/";
158                 try {
159                         path = URLDecoder.decode(path, "UTF-8");
160                 } catch (IllegalArgumentException e) {
161                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
162                         return;
163                 }
164         String progress = req.getParameter(PROGRESS_PARAMETER);
165
166         if (logger.isDebugEnabled())
167                         if (content)
168                         logger.debug("Serving resource '" +     path + "' headers and data");
169                 else
170                         logger.debug("Serving resource '" +     path + "' headers only");
171
172         User user = getUser(req);
173         User owner = getOwner(req);
174         if (user == null) user = owner;
175         boolean exists = true;
176         Object resource = null;
177         FileHeaderDTO file = null;
178         FolderDTO folder = null;
179         try {
180                 resource = getService().getResourceAtPath(owner.getId(), path, false);
181         } catch (ObjectNotFoundException e) {
182             exists = false;
183         } catch (RpcException e) {
184                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
185                         return;
186                 }
187
188         if (!exists) {
189                         if (authDeferred) {
190                                 // We do not want to leak information if the request
191                                 // was not authenticated.
192                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
193                                 return;
194                         }
195                 // A request for upload progress.
196                 if (progress != null && content) {
197                         serveProgress(req, resp, progress, user, null);
198                                 return;
199                 }
200
201                 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
202                 return;
203         }
204
205         if (resource instanceof FolderDTO)
206                 folder = (FolderDTO) resource;
207         else
208                 file = (FileHeaderDTO) resource;
209
210         // Now it's time to perform the deferred authentication check.
211                 // Since regular signature checking was already performed,
212                 // we need to check the read-all flag or the signature-in-parameters.
213                 if (authDeferred)
214                         if (file != null && !file.isReadForAll() && content) {
215                                 // Check for GET with the signature in the request parameters.
216                                 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
217                                 String dateParam = req.getParameter(DATE_PARAMETER);
218                                 if (auth == null || dateParam == null) {
219                                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
220                                         return;
221                                 }
222
223                         long timestamp;
224                                 try {
225                                         timestamp = DateUtil.parseDate(dateParam).getTime();
226                                 } catch (DateParseException e) {
227                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
228                                 return;
229                                 }
230                         if (!isTimeValid(timestamp)) {
231                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
232                                 return;
233                         }
234
235                                 // Fetch the Authorization parameter and find the user specified in it.
236                                 String[] authParts = auth.split(" ");
237                                 if (authParts.length != 2) {
238                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
239                                 return;
240                         }
241                                 String username = authParts[0];
242                                 String signature = authParts[1];
243                                 user = null;
244                                 try {
245                                         user = getService().findUser(username);
246                                 } catch (RpcException e) {
247                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
248                                         return;
249                                 }
250                                 if (user == null) {
251                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
252                                 return;
253                         }
254                                 req.setAttribute(USER_ATTRIBUTE, user);
255
256                                 // Remove the servlet path from the request URI.
257                                 String p = req.getRequestURI();
258                                 String servletPath = req.getContextPath() + req.getServletPath();
259                                 p = p.substring(servletPath.length());
260                                 // Validate the signature in the Authorization parameter.
261                                 String data = req.getMethod() + dateParam + p;
262                                 if (!isSignatureValid(signature, user, data)) {
263                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
264                                 return;
265                         }
266                         } else if (file != null && !file.isReadForAll() || file == null) {
267                                 // Check for a read-for-all file request.
268                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
269                                 return;
270                         }
271
272         // If the resource is not a collection, and the resource path
273         // ends with "/" or "\", return NOT FOUND.
274         if (folder == null)
275                         if (path.endsWith("/") || path.endsWith("\\")) {
276                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
277                         return;
278                 }
279
280         // Workaround for IE's broken caching behavior.
281         if (folder != null)
282                 resp.setHeader("Expires", "-1");
283
284         // A request for upload progress.
285         if (progress != null && content) {
286                 if (file == null) {
287                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
288                         return;
289                 }
290                 serveProgress(req, resp, progress, user, file);
291                         return;
292         }
293
294                 // Fetch the version to retrieve, if specified.
295                 String verStr = req.getParameter(VERSION_PARAM);
296                 int version = 0;
297                 FileBodyDTO oldBody = null;
298                 if (verStr != null && file != null)
299                         try {
300                                 version = Integer.valueOf(verStr);
301                         } catch (NumberFormatException e) {
302                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
303                         return;
304                         }
305                 if (version > 0)
306                         try {
307                                 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
308                         } catch (RpcException e) {
309                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
310                                 return;
311                         } catch (ObjectNotFoundException e) {
312                         resp.sendError(HttpServletResponse.SC_NOT_FOUND);
313                         return;
314                         } catch (InsufficientPermissionsException e) {
315                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
316                         return;
317                         }
318
319         // Check if the conditions specified in the optional If headers are
320         // satisfied. Doing this for folders would require recursive checking
321         // for all of their children, which in turn would defy the purpose of
322         // the optimization.
323         if (folder == null)
324                         // Checking If headers.
325                 if (!checkIfHeaders(req, resp, file, oldBody))
326                                 return;
327
328         // Find content type.
329         String contentType = null;
330         if (file != null) {
331                 contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
332                 if (contentType == null) {
333                         contentType = context.getMimeType(file.getName());
334                         file.setMimeType(contentType);
335                 }
336         } else
337                         contentType = "application/json;charset=UTF-8";
338
339         ArrayList ranges = null;
340         long contentLength = -1L;
341
342         if (file != null) {
343                 // Parse range specifier
344                 ranges = parseRange(req, resp, file, oldBody);
345                 // ETag header
346                 resp.setHeader("ETag", getETag(file, oldBody));
347                 // Last-Modified header
348                 String lastModified = oldBody == null ?
349                                         getLastModifiedHttp(file.getAuditInfo()) :
350                                         getLastModifiedHttp(oldBody.getAuditInfo());
351                 resp.setHeader("Last-Modified", lastModified);
352                 // X-GSS-Metadata header
353                 try {
354                                 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
355                         } catch (InsufficientPermissionsException e) {
356                                 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
357                         return;
358                 }
359                 // Get content length
360                 contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
361                 // Special case for zero length files, which would cause a
362                 // (silent) ISE when setting the output buffer size
363                 if (contentLength == 0L)
364                                 content = false;
365         }
366
367         ServletOutputStream ostream = null;
368         PrintWriter writer = null;
369
370         if (content)
371                         try {
372                         ostream = resp.getOutputStream();
373                 } catch (IllegalStateException e) {
374                         // If it fails, we try to get a Writer instead if we're
375                         // trying to serve a text file
376                         if ( contentType == null
377                                                 || contentType.startsWith("text")
378                                                 || contentType.endsWith("xml") )
379                                         writer = resp.getWriter();
380                                 else
381                                         throw e;
382                 }
383
384         if (folder != null
385                                 || (ranges == null || ranges.isEmpty())
386                                                         && req.getHeader("Range") == null
387                                                         || ranges == FULL) {
388                 // Set the appropriate output headers
389                 if (contentType != null) {
390                         if (logger.isDebugEnabled())
391                                 logger.debug("contentType='" + contentType + "'");
392                         resp.setContentType(contentType);
393                 }
394                 if (file != null && contentLength >= 0) {
395                         if (logger.isDebugEnabled())
396                                 logger.debug("contentLength=" + contentLength);
397                         if (contentLength < Integer.MAX_VALUE)
398                                         resp.setContentLength((int) contentLength);
399                                 else
400                                         // Set the content-length as String to be able to use a long
401                                 resp.setHeader("content-length", "" + contentLength);
402                 }
403
404                 InputStream renderResult = null;
405                 if (folder != null)
406                                 if (content)
407                                         // Serve the directory browser
408                                 try {
409                                                 renderResult = renderJson(user, folder);
410                                         } catch (InsufficientPermissionsException e) {
411                                                 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
412                                         return;
413                                         }
414                 // Copy the input stream to our output stream (if requested)
415                 if (content) {
416                         try {
417                                 resp.setBufferSize(output);
418                         } catch (IllegalStateException e) {
419                                 // Silent catch
420                         }
421                         try {
422                                 if(file != null && needsContentDisposition(req))
423                                                 resp.setHeader("Content-Disposition","attachment; filename=\""+file.getName()+"\"");
424                                 if (ostream != null)
425                                                 copy(file, renderResult, ostream, req, oldBody);
426                                         else
427                                                 copy(file, renderResult, writer, req, oldBody);
428                                 if (file!=null) getService().updateAccounting(user, new Date(), contentLength);
429                         } catch (ObjectNotFoundException e) {
430                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
431                                 return;
432                         } catch (InsufficientPermissionsException e) {
433                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
434                                 return;
435                         } catch (RpcException e) {
436                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
437                                 return;
438                         }
439                 }
440         } else {
441                 if (ranges == null || ranges.isEmpty())
442                         return;
443                 // Partial content response.
444                 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
445
446                 if (ranges.size() == 1) {
447                         Range range = (Range) ranges.get(0);
448                         resp.addHeader("Content-Range", "bytes "
449                                                 + range.start
450                                                 + "-" + range.end + "/"
451                                                 + range.length);
452                         long length = range.end - range.start + 1;
453                         if (length < Integer.MAX_VALUE)
454                                         resp.setContentLength((int) length);
455                                 else
456                                         // Set the content-length as String to be able to use a long
457                                 resp.setHeader("content-length", "" + length);
458
459                         if (contentType != null) {
460                                 if (logger.isDebugEnabled())
461                                         logger.debug("contentType='" + contentType + "'");
462                                 resp.setContentType(contentType);
463                         }
464
465                         if (content) {
466                                 try {
467                                         resp.setBufferSize(output);
468                                 } catch (IllegalStateException e) {
469                                         // Silent catch
470                                 }
471                                 try {
472                                         if (ostream != null)
473                                                         copy(file, ostream, range, req, oldBody);
474                                                 else
475                                                         copy(file, writer, range, req, oldBody);
476                                         getService().updateAccounting(user, new Date(), contentLength);
477                         } catch (ObjectNotFoundException e) {
478                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
479                                 return;
480                         } catch (InsufficientPermissionsException e) {
481                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
482                                 return;
483                         } catch (RpcException e) {
484                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
485                                 return;
486                         }
487                         }
488                 } else {
489                         resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
490                         if (content) {
491                                 try {
492                                         resp.setBufferSize(output);
493                                 } catch (IllegalStateException e) {
494                                         // Silent catch
495                                 }
496                                 try {
497                                         if (ostream != null)
498                                                         copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
499                                                 else
500                                                         copy(file, writer, ranges.iterator(), contentType, req, oldBody);
501                                         getService().updateAccounting(user, new Date(), contentLength);
502                         } catch (ObjectNotFoundException e) {
503                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
504                                 return;
505                         } catch (InsufficientPermissionsException e) {
506                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
507                                 return;
508                         } catch (RpcException e) {
509                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
510                                 return;
511                         }
512                         }
513                 }
514         }
515     }
516
517         /**
518          * Determines whether the user agent needs the Content-Disposition
519          * header to be set, in order to properly download a file.
520          *
521          * @param req the HTTP request
522          * @return true if the Content-Disposition HTTP header must be set
523          */
524         private boolean needsContentDisposition(HttpServletRequest req) {
525                 if (req.getHeader("user-agent").contains("MSIE"))
526                         return true;
527                 return false;
528         }
529
530         /**
531          * Sends a progress update on the amount of bytes received until now for
532          * a file that the current user is currently uploading.
533          *
534          * @param req the HTTP request
535          * @param resp the HTTP response
536          * @param parameter the value for the progress request parameter
537          * @param user the current user
538          * @param file the file being uploaded, or null if the request is about a new file
539          * @throws IOException if an I/O error occurs
540          */
541         private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
542                                 String parameter, User user, FileHeaderDTO file)        throws IOException {
543                 String filename = file == null ? parameter : file.getName();
544                 try {
545                         FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
546                         if (status == null) {
547                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
548                                 return;
549                         }
550                         JSONObject json = new JSONObject();
551                         json.put("bytesUploaded", status.getBytesUploaded()).
552                                 put("bytesTotal", status.getFileSize());
553                         sendJson(req, resp, json.toString());
554
555                         // Workaround for IE's broken caching behavior.
556                 resp.setHeader("Expires", "-1");
557                         return;
558                 } catch (RpcException e) {
559                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
560                         return;
561                 } catch (JSONException e) {
562                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
563                         return;
564                 }
565         }
566
567         /**
568          * Server a POST request to create/modify a file or folder.
569          *
570          * @param req the HTTP request
571          * @param resp the HTTP response
572      * @exception IOException if an input/output error occurs
573          */
574         void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
575                 boolean authDeferred = getAuthDeferred(req);
576         if (!authDeferred && req.getParameterMap().size() > 1) {
577                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
578                 return;
579         }
580         String path = getInnerPath(req, PATH_FILES);
581         path = path.endsWith("/")? path: path + '/';
582                 try {
583                 path = URLDecoder.decode(path, "UTF-8");
584                 } catch (IllegalArgumentException e) {
585                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
586                         return;
587                 }
588         // We only defer authenticating multipart POST requests.
589         if (authDeferred) {
590                         if (!ServletFileUpload.isMultipartContent(req)) {
591                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
592                         return;
593                 }
594                         handleMultipart(req, resp, path);
595                         return;
596                 }
597
598         String newName = req.getParameter(NEW_FOLDER_PARAMETER);
599         boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
600         boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
601         boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
602         String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
603         String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
604         String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
605
606         if (newName != null) {
607                 try {
608                         newName = URLDecoder.decode(newName, "UTF-8");
609                 } catch (IllegalArgumentException e) {
610                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
611                         return;
612                 }
613                         createFolder(req, resp, path, newName);
614         } else if (hasUpdateParam)
615                         updateResource(req, resp, path);
616                 else if (hasTrashParam)
617                         trashResource(req, resp, path);
618                 else if (hasRestoreParam)
619                         restoreResource(req, resp, path);
620                 else if (copyTo != null)
621                         copyResource(req, resp, path, copyTo);
622                 else if (moveTo != null)
623                         moveResource(req, resp, path, moveTo);
624                 else if (restoreVersion != null)
625                         restoreVersion(req, resp, path, restoreVersion);
626                 else
627                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
628         }
629
630         /**
631          * Restores a previous version for a file.
632          *
633          * @param req the HTTP request
634          * @param resp the HTTP response
635          * @param path the resource path
636          * @param version the version number to restore
637          * @throws IOException if an I/O error occurs
638          */
639         private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
640                 User user = getUser(req);
641                 User owner = getOwner(req);
642                 Object resource = null;
643                 try {
644                         resource = getService().getResourceAtPath(owner.getId(), path, true);
645                 } catch (ObjectNotFoundException e) {
646                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
647                         return;
648                 } catch (RpcException e) {
649                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
650                         return;
651                 }
652                 if (resource instanceof FolderDTO) {
653                         resp.sendError(HttpServletResponse.SC_CONFLICT);
654                         return;
655                 }
656
657                 try {
658                         FileHeaderDTO file = (FileHeaderDTO) resource;
659                         int oldVersion = Integer.parseInt(version);
660                         getService().restoreVersion(user.getId(), file.getId(), oldVersion);
661                 } catch (InsufficientPermissionsException e) {
662                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
663                 } catch (ObjectNotFoundException e) {
664                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
665                 } catch (RpcException e) {
666                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
667                 } catch (GSSIOException e) {
668                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
669                 } catch (QuotaExceededException e) {
670                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
671                 } catch (NumberFormatException e) {
672                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
673                 }
674         }
675
676         /**
677          * A method for handling multipart POST requests for uploading
678          * files from browser-based JavaScript clients.
679          *
680          * @param request the HTTP request
681          * @param response the HTTP response
682          * @param path the resource path
683          * @throws IOException in case an error occurs writing to the
684          *              response stream
685          */
686         private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
687         if (logger.isDebugEnabled())
688                         logger.debug("Multipart POST for resource: " + path);
689
690         User owner = getOwner(request);
691         boolean exists = true;
692         Object resource = null;
693         FileHeaderDTO file = null;
694         try {
695                 resource = getService().getResourceAtPath(owner.getId(), path, false);
696         } catch (ObjectNotFoundException e) {
697             exists = false;
698         } catch (RpcException e) {
699                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
700                         return;
701                 }
702
703         if (exists)
704                         if (resource instanceof FileHeaderDTO) {
705                         file = (FileHeaderDTO) resource;
706                         if (file.isDeleted()) {
707                                 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
708                         return;
709                         }
710                         } else {
711                         response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
712                         return;
713                 }
714
715         FolderDTO folder = null;
716         Object parent;
717         String parentPath = null;
718                 try {
719                         parentPath = getParentPath(path);
720                         parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
721                 } catch (ObjectNotFoundException e) {
722                 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
723                 return;
724                 } catch (RpcException e) {
725                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
726                         return;
727                 }
728         if (!(parent instanceof FolderDTO)) {
729                 response.sendError(HttpServletResponse.SC_CONFLICT);
730                 return;
731         }
732                 folder = (FolderDTO) parent;
733         String fileName = getLastElement(path);
734
735                 FileItemIterator iter;
736                 File uploadedFile = null;
737                 try {
738                         // Create a new file upload handler.
739                         ServletFileUpload upload = new ServletFileUpload();
740                         StatusProgressListener progressListener = new StatusProgressListener(getService());
741                         upload.setProgressListener(progressListener);
742                         iter = upload.getItemIterator(request);
743                         String dateParam = null;
744                         String auth = null;
745                         while (iter.hasNext()) {
746                                 FileItemStream item = iter.next();
747                                 String name = item.getFieldName();
748                                 InputStream stream = item.openStream();
749                                 if (item.isFormField()) {
750                                         final String value = Streams.asString(stream);
751                                         if (name.equals(DATE_PARAMETER))
752                                                 dateParam = value;
753                                         else if (name.equals(AUTHORIZATION_PARAMETER))
754                                                 auth = value;
755
756                                         if (logger.isDebugEnabled())
757                                                 logger.debug(name + ":" + value);
758                                 } else {
759                                         // Fetch the timestamp used to guard against replay attacks.
760                                 if (dateParam == null) {
761                                         response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
762                                         return;
763                                 }
764
765                                 long timestamp;
766                                         try {
767                                                 timestamp = DateUtil.parseDate(dateParam).getTime();
768                                         } catch (DateParseException e) {
769                                         response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
770                                         return;
771                                         }
772                                 if (!isTimeValid(timestamp)) {
773                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
774                                         return;
775                                 }
776
777                                         // Fetch the Authorization parameter and find the user specified in it.
778                                 if (auth == null) {
779                                         response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
780                                         return;
781                                 }
782                                         String[] authParts = auth.split(" ");
783                                         if (authParts.length != 2) {
784                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
785                                         return;
786                                 }
787                                         String username = authParts[0];
788                                         String signature = authParts[1];
789                                         User user = null;
790                                         try {
791                                                 user = getService().findUser(username);
792                                         } catch (RpcException e) {
793                                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
794                                                 return;
795                                         }
796                                         if (user == null) {
797                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
798                                         return;
799                                 }
800                                         request.setAttribute(USER_ATTRIBUTE, user);
801
802                                         // Remove the servlet path from the request URI.
803                                         String p = request.getRequestURI();
804                                         String servletPath = request.getContextPath() + request.getServletPath();
805                                         p = p.substring(servletPath.length());
806                                         // Validate the signature in the Authorization parameter.
807                                         String data = request.getMethod() + dateParam + p;
808                                         if (!isSignatureValid(signature, user, data)) {
809                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
810                                         return;
811                                 }
812
813                                         progressListener.setUserId(user.getId());
814                                         progressListener.setFilename(fileName);
815                                         String contentType = item.getContentType();
816
817                                         try {
818                                                 uploadedFile = getService().uploadFile(stream, user.getId());
819                                         } catch (IOException ex) {
820                                                 throw new GSSIOException(ex, false);
821                                         }
822                                         FileHeaderDTO fileDTO = null;
823                                         if (file == null)
824                                                 fileDTO = getService().createFile(user.getId(), folder.getId(), fileName, contentType, uploadedFile);
825                                         else
826                                                 fileDTO = getService().updateFileContents(user.getId(), file.getId(), contentType, uploadedFile);
827                                         getService().updateAccounting(user, new Date(), fileDTO.getFileSize());
828                                         getService().removeFileUploadProgress(user.getId(), fileName);
829                                 }
830                         }
831                         // We can't return 204 here since GWT's onSubmitComplete won't fire.
832                         response.setContentType("text/html");
833             response.getWriter().print("<pre></pre>");
834                 } catch (FileUploadException e) {
835                         String error = "Error while uploading file";
836                         logger.error(error, e);
837                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
838                 } catch (GSSIOException e) {
839                         if (uploadedFile != null && uploadedFile.exists())
840                                 uploadedFile.delete();
841                         String error = "Error while uploading file";
842                         if (e.logAsError())
843                                 logger.error(error, e);
844                         else
845                                 logger.debug(error, e);
846                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
847                 } catch (DuplicateNameException e) {
848                         if (uploadedFile != null && uploadedFile.exists())
849                                 uploadedFile.delete();
850                         String error = "The specified file name already exists in this folder";
851                         logger.error(error, e);
852                         response.sendError(HttpServletResponse.SC_CONFLICT, error);
853
854                 } catch (InsufficientPermissionsException e) {
855                         if (uploadedFile != null && uploadedFile.exists())
856                                 uploadedFile.delete();
857                         String error = "You don't have the necessary permissions";
858                         logger.error(error, e);
859                         response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
860
861                 } catch (QuotaExceededException e) {
862                         if (uploadedFile != null && uploadedFile.exists())
863                                 uploadedFile.delete();
864                         String error = "Not enough free space available";
865                         if (logger.isDebugEnabled())
866                                 logger.debug(error, e);
867                         response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
868
869                 } catch (ObjectNotFoundException e) {
870                         if (uploadedFile != null && uploadedFile.exists())
871                                 uploadedFile.delete();
872                         String error = "A specified object was not found";
873                         logger.error(error, e);
874                         response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
875                 } catch (RpcException e) {
876                         if (uploadedFile != null && uploadedFile.exists())
877                                 uploadedFile.delete();
878                         String error = "An error occurred while communicating with the service";
879                         logger.error(error, e);
880                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
881                 }
882         }
883
884         private String getBackupFilename(FileHeaderDTO file, String filename){
885                 List<FileHeaderDTO> deletedFiles = new ArrayList<FileHeaderDTO>();
886                 try{
887                         deletedFiles = getService().getDeletedFiles(file.getOwner().getId());
888                 }
889                 catch(ObjectNotFoundException e){
890
891                 } catch (RpcException e) {
892
893                 }
894                 List<FileHeaderDTO> filesInSameFolder = new ArrayList<FileHeaderDTO>();
895                 for(FileHeaderDTO deleted : deletedFiles)
896                         if(deleted.getFolder().getId().equals(file.getFolder().getId()))
897                                 filesInSameFolder.add(deleted);
898                 int i=1;
899                 String filenameToCheck = filename;
900                 for(FileHeaderDTO same : filesInSameFolder)
901                         if(same.getName().startsWith(filename)){
902                                 String toCheck=same.getName().substring(filename.length(),same.getName().length());
903                                 if(toCheck.startsWith(" ")){
904                                         int test =-1;
905                                         try{
906                                                 test = Integer.valueOf(toCheck.replace(" ",""));
907                                         }
908                                         catch(NumberFormatException e){
909                                                 //do nothing since string is not a number
910                                         }
911                                         if(test>=i)
912                                                 i = test+1;
913                                 }
914                         }
915
916                 return filename+" "+i;
917         }
918
919         /**
920          * Move the resource in the specified path to the specified destination.
921          *
922          * @param req the HTTP request
923          * @param resp the HTTP response
924          * @param path the path of the resource
925          * @param moveTo the destination of the move procedure
926          * @throws IOException if an input/output error occurs
927          */
928         private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
929                 User user = getUser(req);
930                 User owner = getOwner(req);
931                 Object resource = null;
932                 try {
933                         resource = getService().getResourceAtPath(owner.getId(), path, true);
934                 } catch (ObjectNotFoundException e) {
935                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
936                         return;
937                 } catch (RpcException e) {
938                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
939                         return;
940                 }
941
942         String destination = null;
943         User destOwner = null;
944                 boolean exists = true;
945                 try {
946                         destination = getDestinationPath(req, encodePath(moveTo));
947                         destination = URLDecoder.decode(destination, "UTF-8");
948                         destOwner = getDestinationOwner(req);
949                         getService().getResourceAtPath(destOwner.getId(), destination, true);
950                 } catch (ObjectNotFoundException e) {
951                         exists = false;
952                 } catch (URISyntaxException e) {
953                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
954                         return;
955                 } catch (RpcException e) {
956                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
957                         return;
958                 }
959                 if (exists) {
960                         resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
961                         return;
962                 }
963
964                 try {
965                         if (resource instanceof FolderDTO) {
966                                 FolderDTO folder = (FolderDTO) resource;
967                                 getService().moveFolderToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
968                         } else {
969                                 FileHeaderDTO file = (FileHeaderDTO) resource;
970                                 getService().moveFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
971                         }
972                 } catch (InsufficientPermissionsException e) {
973                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
974                 } catch (ObjectNotFoundException e) {
975                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
976                 } catch (RpcException e) {
977                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
978                 } catch (DuplicateNameException e) {
979                         resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
980                 } catch (GSSIOException e) {
981                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
982                 } catch (QuotaExceededException e) {
983                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
984                 }
985         }
986
987         /**
988          * Copy the resource in the specified path to the specified destination.
989          *
990          * @param req the HTTP request
991          * @param resp the HTTP response
992          * @param path the path of the resource
993          * @param copyTo the destination of the copy procedure
994          * @throws IOException if an input/output error occurs
995          */
996         private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
997                 User user = getUser(req);
998                 User owner = getOwner(req);
999                 Object resource = null;
1000                 try {
1001                         resource = getService().getResourceAtPath(owner.getId(), path, true);
1002                 } catch (ObjectNotFoundException e) {
1003                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1004                         return;
1005                 } catch (RpcException e) {
1006                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1007                         return;
1008                 }
1009
1010         String destination = null;
1011         User destOwner = null;
1012                 boolean exists = true;
1013                 try {
1014                         destination = getDestinationPath(req, encodePath(copyTo));
1015                         destination = URLDecoder.decode(destination, "UTF-8");
1016                         destOwner = getDestinationOwner(req);
1017                         getService().getResourceAtPath(destOwner.getId(), destination, true);
1018                 } catch (ObjectNotFoundException e) {
1019                         exists = false;
1020                 } catch (URISyntaxException e) {
1021                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1022                         return;
1023                 } catch (RpcException e) {
1024                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1025                         return;
1026                 }
1027                 if (exists) {
1028                         resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1029                         return;
1030                 }
1031
1032                 try {
1033                         if (resource instanceof FolderDTO) {
1034                                 FolderDTO folder = (FolderDTO) resource;
1035                                 getService().copyFolderStructureToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
1036                         } else {
1037                                 FileHeaderDTO file = (FileHeaderDTO) resource;
1038                                 getService().copyFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
1039                         }
1040                 } catch (InsufficientPermissionsException e) {
1041                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1042                 } catch (ObjectNotFoundException e) {
1043                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1044                 } catch (RpcException e) {
1045                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1046                 } catch (DuplicateNameException e) {
1047                         resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1048                 } catch (GSSIOException e) {
1049                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1050                 } catch (QuotaExceededException e) {
1051                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1052                 }
1053         }
1054
1055         private String encodePath(String path) throws UnsupportedEncodingException{
1056                 StringTokenizer str = new StringTokenizer(path, "/:", true);
1057                 String result = new String();
1058                 while(str.hasMoreTokens()){
1059                         String token = str.nextToken();
1060                         if(!token.equals("/") && !token.equals(":"))
1061                                 token = URLEncoder.encode(token,"UTF-8");
1062                         result = result + token;
1063                 }
1064                 return result;
1065         }
1066         /**
1067          * A helper method that extracts the relative resource path,
1068          * after removing the 'files' namespace.
1069          *
1070          * @param req the HTTP request
1071          * @param path the specified path
1072          * @return the path relative to the root folder
1073          * @throws URISyntaxException
1074          * @throws RpcException in case an error occurs while communicating
1075          *                                              with the backend
1076          */
1077         private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException {
1078                 URI uri = new URI(path);
1079                 String dest = uri.getPath();
1080                 // Remove the context path from the destination URI.
1081                 String contextPath = req.getContextPath();
1082                 if (!dest.startsWith(contextPath))
1083                         throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1084                 dest = dest.substring(contextPath.length());
1085                 // Remove the servlet path from the destination URI.
1086                 String servletPath = req.getServletPath();
1087                 if (!dest.startsWith(servletPath))
1088                         throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1089                 dest = dest.substring(servletPath.length());
1090         // Strip the username part
1091                 if (dest.length() < 2)
1092                         throw new URISyntaxException(dest, "No username in the destination URI");
1093                 int slash = dest.substring(1).indexOf('/');
1094                 if (slash == -1)
1095                         throw new URISyntaxException(dest, "No username in the destination URI");
1096                 String owner = dest.substring(1, slash + 1);
1097                 User o;
1098                 o = getService().findUser(owner);
1099                 if (o == null)
1100                         throw new URISyntaxException(dest, "User " + owner + " not found");
1101
1102                 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1103                 dest = dest.substring(slash + 1);
1104
1105                 // Chop the resource namespace part
1106                 dest = dest.substring(RequestHandler.PATH_FILES.length());
1107
1108         dest = dest.endsWith("/")? dest: dest + '/';
1109                 return dest;
1110         }
1111
1112         /**
1113          * Move the resource in the specified path to the trash bin.
1114          *
1115          * @param req the HTTP request
1116          * @param resp the HTTP response
1117          * @param path the path of the resource
1118          * @throws IOException if an input/output error occurs
1119          */
1120         private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1121                 User user = getUser(req);
1122                 User owner = getOwner(req);
1123                 Object resource = null;
1124                 try {
1125                         resource = getService().getResourceAtPath(owner.getId(), path, true);
1126                 } catch (ObjectNotFoundException e) {
1127                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1128                         return;
1129                 } catch (RpcException e) {
1130                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1131                         return;
1132                 }
1133
1134                 try {
1135                         if (resource instanceof FolderDTO) {
1136                                 FolderDTO folder = (FolderDTO) resource;
1137                                 getService().moveFolderToTrash(user.getId(), folder.getId());
1138                         } else {
1139                                 FileHeaderDTO file = (FileHeaderDTO) resource;
1140                                 getService().moveFileToTrash(user.getId(), file.getId());
1141                         }
1142                 } catch (InsufficientPermissionsException e) {
1143                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1144                 } catch (ObjectNotFoundException e) {
1145                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1146                 } catch (RpcException e) {
1147                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1148                 }
1149         }
1150
1151         /**
1152          * Restore the resource in the specified path from the trash bin.
1153          *
1154          * @param req the HTTP request
1155          * @param resp the HTTP response
1156          * @param path the path of the resource
1157          * @throws IOException if an input/output error occurs
1158          */
1159         private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1160                 User user = getUser(req);
1161                 User owner = getOwner(req);
1162                 Object resource = null;
1163                 try {
1164                         resource = getService().getResourceAtPath(owner.getId(), path, false);
1165                 } catch (ObjectNotFoundException e) {
1166                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1167                         return;
1168                 } catch (RpcException e) {
1169                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1170                         return;
1171                 }
1172
1173                 try {
1174                         if (resource instanceof FolderDTO) {
1175                                 FolderDTO folder = (FolderDTO) resource;
1176                                 getService().removeFolderFromTrash(user.getId(), folder.getId());
1177                         } else {
1178                                 FileHeaderDTO file = (FileHeaderDTO) resource;
1179                                 getService().removeFileFromTrash(user.getId(), file.getId());
1180                         }
1181                 } catch (InsufficientPermissionsException e) {
1182                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1183                 } catch (ObjectNotFoundException e) {
1184                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1185                 } catch (RpcException e) {
1186                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1187                 }
1188         }
1189
1190         /**
1191          * Update the resource in the specified path.
1192          *
1193          * @param req the HTTP request
1194          * @param resp the HTTP response
1195          * @param path the path of the resource
1196          * @throws IOException if an input/output error occurs
1197          */
1198         private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1199                 User user = getUser(req);
1200                 User owner = getOwner(req);
1201                 Object resource = null;
1202                 try {
1203                         resource = getService().getResourceAtPath(owner.getId(), path, false);
1204                 } catch (ObjectNotFoundException e) {
1205                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1206                         return;
1207                 } catch (RpcException e) {
1208                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1209                         return;
1210                 }
1211                 //use utf-8 encoding for reading request
1212                 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1213                 StringBuffer input = new StringBuffer();
1214                 String line = null;
1215                 JSONObject json = null;
1216                 while ((line = reader.readLine()) != null)
1217                         input.append(line);
1218                 reader.close();
1219                 try {
1220                         json = new JSONObject(input.toString());
1221                         if (logger.isDebugEnabled())
1222                                 logger.debug("JSON update: " + json);
1223                         if (resource instanceof FolderDTO) {
1224                                 FolderDTO folder = (FolderDTO) resource;
1225                                 String name = json.optString("name");
1226                                 if (!name.isEmpty()){
1227                                         try {
1228                                                 name = URLDecoder.decode(name, "UTF-8");
1229                                         } catch (IllegalArgumentException e) {
1230                                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1231                                                 return;
1232                                         }
1233                                         getService().modifyFolder(user.getId(), folder.getId(), name);
1234                                         FolderDTO folderUpdated = getService().getFolder(user.getId(), folder.getId());
1235                                         String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1236                                         String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1237                                         parentUrl = parentUrl.replaceAll(fpath, "");
1238                                         if(!parentUrl.endsWith("/"))
1239                                                 parentUrl = parentUrl+"/";
1240                                         parentUrl = parentUrl+folderUpdated.getOwner().getUsername()+PATH_FILES+folderUpdated.getPath();
1241                                         resp.getWriter().println(parentUrl);
1242                                 }
1243
1244                                 JSONArray permissions = json.optJSONArray("permissions");
1245                                 if (permissions != null) {
1246                                         Set<PermissionDTO> perms = parsePermissions(user, permissions);
1247                                         getService().setFolderPermissions(user.getId(), folder.getId(), perms);
1248                                 }
1249                         } else {
1250                                 FileHeaderDTO file = (FileHeaderDTO) resource;
1251                                 String name = null;
1252                                 if (json.opt("name") != null)
1253                                         name = json.optString("name");
1254                                 JSONArray tagset = json.optJSONArray("tags");
1255                                 String tags = null;
1256                                 StringBuffer t = new StringBuffer();
1257                                 if (tagset != null) {
1258                                         for (int i = 0; i < tagset.length(); i++)
1259                                                 t.append(tagset.getString(i) + ',');
1260                                         tags = t.toString();
1261                                 }
1262                                 if (name != null || tags != null)
1263                                         getService().updateFile(user.getId(), file.getId(), name, tags);
1264
1265                                 JSONArray permissions = json.optJSONArray("permissions");
1266                                 Set<PermissionDTO> perms = null;
1267                                 if (permissions != null)
1268                                         perms = parsePermissions(user, permissions);
1269                                 Boolean readForAll = null;
1270                                 if (json.opt("readForAll") != null)
1271                                         readForAll = json.optBoolean("readForAll");
1272                                 if (perms != null || readForAll != null)
1273                                         getService().setFilePermissions(user.getId(), file.getId(), readForAll, perms);
1274
1275                                 if (json.opt("versioned") != null) {
1276                                         boolean versioned = json.getBoolean("versioned");
1277                                         getService().toggleFileVersioning(user.getId(), file.getId(), versioned);
1278                                 }
1279                         }
1280                 } catch (JSONException e) {
1281                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1282                 } catch (InsufficientPermissionsException e) {
1283                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1284                 } catch (ObjectNotFoundException e) {
1285                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1286                 } catch (DuplicateNameException e) {
1287                         resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1288                 } catch (RpcException e) {
1289                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1290                 }
1291         }
1292
1293         /**
1294          * Helper method to convert a JSON array of permissions into a set of
1295          * PermissionDTO objects.
1296          *
1297          * @param user the current user
1298          * @param permissions the JSON array to parse
1299          * @return the parsed set of permissions
1300          * @throws JSONException if there was an error parsing the JSON object
1301          * @throws RpcException if there was an error communicating with the EJB
1302          * @throws ObjectNotFoundException if the user could not be found
1303          */
1304         private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1305                         throws JSONException, RpcException, ObjectNotFoundException {
1306                 if (permissions == null)
1307                         return null;
1308                 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1309                 for (int i = 0; i < permissions.length(); i++) {
1310                         JSONObject j = permissions.getJSONObject(i);
1311                         PermissionDTO perm = new PermissionDTO();
1312                         perm.setModifyACL(j.optBoolean("modifyACL"));
1313                         perm.setRead(j.optBoolean("read"));
1314                         perm.setWrite(j.optBoolean("write"));
1315                         String permUser = j.optString("user");
1316                         if (!permUser.isEmpty()) {
1317                                 User u = getService().findUser(permUser);
1318                                 if (u == null)
1319                                         throw new ObjectNotFoundException("User " + permUser + " not found");
1320                                 perm.setUser(u.getDTO());
1321                         }
1322                         String permGroup = j.optString("group");
1323                         if (!permGroup.isEmpty()) {
1324                                 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1325                                 perm.setGroup(g);
1326                         }
1327                         if (permUser.isEmpty() && permGroup.isEmpty() ||
1328                                                 permUser.isEmpty() && permGroup.isEmpty())
1329                                 throw new JSONException("A permission must correspond to either a user or a group");
1330                         perms.add(perm);
1331                 }
1332                 return perms;
1333         }
1334
1335         /**
1336          * Creates a new folder with the specified name under the folder in the provided path.
1337          *
1338          * @param req the HTTP request
1339          * @param resp the HTTP response
1340          * @param path the parent folder path
1341          * @param folderName the name of the new folder
1342          * @throws IOException if an input/output error occurs
1343          */
1344         private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, String folderName) throws IOException {
1345                 if (logger.isDebugEnabled())
1346                         logger.debug("Creating folder " + folderName + " in '" + path);
1347
1348         User user = getUser(req);
1349         User owner = getOwner(req);
1350         boolean exists = true;
1351         try {
1352                 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1353         } catch (ObjectNotFoundException e) {
1354             exists = false;
1355         } catch (RpcException e) {
1356                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1357                         return;
1358                 }
1359
1360         if (exists) {
1361             resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1362                                 ", " + METHOD_HEAD);
1363             resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1364             return;
1365         }
1366
1367                 Object parent;
1368                 try {
1369                         parent = getService().getResourceAtPath(owner.getId(), path, true);
1370                 } catch (ObjectNotFoundException e) {
1371                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1372                         return;
1373                 } catch (RpcException e) {
1374                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1375                         return;
1376                 }
1377                 try {
1378                         if (parent instanceof FolderDTO) {
1379                                 FolderDTO folder = (FolderDTO) parent;
1380                                 getService().createFolder(user.getId(), folder.getId(), folderName);
1381                         String newResource = getContextPath(req, true) + folderName;
1382                         resp.setHeader("Location", newResource);
1383                         resp.setContentType("text/plain");
1384                     PrintWriter out = resp.getWriter();
1385                     out.println(newResource);
1386                         } else {
1387                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1388                         return;
1389                         }
1390                 } catch (DuplicateNameException e) {
1391                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1392                 return;
1393                 } catch (InsufficientPermissionsException e) {
1394                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1395                 return;
1396                 } catch (ObjectNotFoundException e) {
1397                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1398                         return;
1399                 } catch (RpcException e) {
1400                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1401                         return;
1402                 }
1403         resp.setStatus(HttpServletResponse.SC_CREATED);
1404         }
1405
1406         /**
1407          * @param req
1408          * @param resp
1409          * @throws IOException
1410          * @throws FileNotFoundException
1411          */
1412         void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1413         String path = getInnerPath(req, PATH_FILES);
1414         if (logger.isDebugEnabled())
1415                         logger.debug("Updating resource: " + path);
1416
1417         User user = getUser(req);
1418         User owner = getOwner(req);
1419         boolean exists = true;
1420         Object resource = null;
1421         FileHeaderDTO file = null;
1422         try {
1423                 resource = getService().getResourceAtPath(owner.getId(), path, false);
1424         } catch (ObjectNotFoundException e) {
1425             exists = false;
1426         } catch (RpcException e) {
1427                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1428                         return;
1429                 }
1430
1431         if (exists)
1432                         if (resource instanceof FileHeaderDTO)
1433                         file = (FileHeaderDTO) resource;
1434                         else {
1435                         resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1436                         return;
1437                 }
1438         boolean result = true;
1439
1440         // Temporary content file used to support partial PUT.
1441         File contentFile = null;
1442
1443         Range range = parseContentRange(req, resp);
1444
1445         InputStream resourceInputStream = null;
1446
1447         // Append data specified in ranges to existing content for this
1448         // resource - create a temporary file on the local filesystem to
1449         // perform this operation.
1450         // Assume just one range is specified for now
1451         if (range != null) {
1452             try {
1453                                 contentFile = executePartialPut(req, range, path);
1454                         } catch (RpcException e) {
1455                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1456                                 return;
1457                         } catch (ObjectNotFoundException e) {
1458                                 resp.sendError(HttpServletResponse.SC_CONFLICT);
1459                         return;
1460                         } catch (InsufficientPermissionsException e) {
1461                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1462                         return;
1463                         }
1464             resourceInputStream = new FileInputStream(contentFile);
1465         } else
1466                         resourceInputStream = req.getInputStream();
1467
1468         try {
1469                 FolderDTO folder = null;
1470                 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1471                 if (!(parent instanceof FolderDTO)) {
1472                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1473                         return;
1474                 }
1475                 folder = (FolderDTO) parent;
1476                 String name = getLastElement(path);
1477                 String mimeType = context.getMimeType(name);
1478             // FIXME: Add attributes
1479                 FileHeaderDTO fileDTO = null;
1480             if (exists)
1481                 fileDTO = getService().updateFileContents(user.getId(), file.getId(), mimeType, resourceInputStream);
1482                         else
1483                                 fileDTO = getService().createFile(user.getId(), folder.getId(), name, mimeType, resourceInputStream);
1484             getService().updateAccounting(user, new Date(), fileDTO.getFileSize());
1485                         getService().removeFileUploadProgress(user.getId(), file.getName());
1486         } catch(ObjectNotFoundException e) {
1487             result = false;
1488         } catch (RpcException e) {
1489                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1490                         return;
1491         } catch (IOException e) {
1492                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1493                         return;
1494                 } catch (GSSIOException e) {
1495                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1496                         return;
1497                 } catch (DuplicateNameException e) {
1498                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1499                 return;
1500                 } catch (InsufficientPermissionsException e) {
1501                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1502                 return;
1503                 } catch (QuotaExceededException e) {
1504                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1505                 return;
1506                 }
1507
1508         if (result) {
1509             if (exists)
1510                                 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1511                         else
1512                                 resp.setStatus(HttpServletResponse.SC_CREATED);
1513         } else
1514                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1515         }
1516
1517     /**
1518      * Delete a resource.
1519      *
1520      * @param req The servlet request we are processing
1521      * @param resp The servlet response we are processing
1522          * @throws IOException if the response cannot be sent
1523      */
1524     void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1525         String path = getInnerPath(req, PATH_FILES);
1526         if (logger.isDebugEnabled())
1527                         logger.debug("Deleting resource '" + path);
1528         path = URLDecoder.decode(path, "UTF-8");
1529         User user = getUser(req);
1530         User owner = getOwner(req);
1531         boolean exists = true;
1532         Object object = null;
1533         try {
1534                 object = getService().getResourceAtPath(owner.getId(), path, false);
1535         } catch (ObjectNotFoundException e) {
1536                 exists = false;
1537         } catch (RpcException e) {
1538                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1539                         return;
1540                 }
1541
1542         if (!exists) {
1543                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1544                 return;
1545         }
1546
1547         FolderDTO folder = null;
1548         FileHeaderDTO file = null;
1549         if (object instanceof FolderDTO)
1550                 folder = (FolderDTO) object;
1551         else
1552                 file = (FileHeaderDTO) object;
1553
1554         if (file != null)
1555                         try {
1556                                 getService().deleteFile(user.getId(), file.getId());
1557                 } catch (InsufficientPermissionsException e) {
1558                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1559                                 return;
1560                 } catch (ObjectNotFoundException e) {
1561                         // Although we had already found the object, it was
1562                         // probably deleted from another thread.
1563                         resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1564                         return;
1565                 } catch (RpcException e) {
1566                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1567                         return;
1568                 }
1569                 else if (folder != null)
1570                         try {
1571                         getService().deleteFolder(user.getId(), folder.getId());
1572                 } catch (InsufficientPermissionsException e) {
1573                         resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1574                         return;
1575                 } catch (ObjectNotFoundException e) {
1576                         resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1577                         return;
1578                 } catch (RpcException e) {
1579                         resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1580                         return;
1581                 }
1582                 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1583         return;
1584     }
1585
1586         /**
1587      * Return an InputStream to a JSON representation of the contents
1588      * of this directory.
1589      *
1590          * @param user the user that made the request
1591      * @param folder the specified directory
1592      * @return an input stream with the rendered contents
1593          * @throws IOException if the response cannot be sent
1594      * @throws ServletException
1595          * @throws InsufficientPermissionsException if the user does not have
1596          *                      the necessary privileges to read the directory
1597      */
1598     private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1599                 ServletException, InsufficientPermissionsException {
1600         JSONObject json = new JSONObject();
1601         try {
1602                         json.put("name", folder.getName()).
1603                                         put("owner", folder.getOwner().getUsername()).
1604                                         put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1605                                         put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1606                                         put("deleted", folder.isDeleted());
1607                         if (folder.getParent() != null) {
1608                                 JSONObject j = new JSONObject();
1609                                 j.put("uri", getApiRoot() + folder.getParent().getURI());
1610                                 j.put("name", folder.getParent().getName());
1611                                 json.put("parent", j);
1612                         }
1613                         if (folder.getAuditInfo().getModifiedBy() != null)
1614                                 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1615                                                 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1616                 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1617                 for (FolderDTO f: folder.getSubfolders())
1618                                 if (!f.isDeleted()) {
1619                                         JSONObject j = new JSONObject();
1620                                         j.put("name", f.getName()).
1621                                                 put("uri", getApiRoot() + f.getURI());
1622                                         subfolders.add(j);
1623                                 }
1624                 json.put("folders", subfolders);
1625                 List<JSONObject> files = new ArrayList<JSONObject>();
1626                 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1627                 for (FileHeaderDTO f: fileHeaders) {
1628                         JSONObject j = new JSONObject();
1629                                 j.put("name", f.getName()).
1630                                         put("owner", f.getOwner().getUsername()).
1631                                         put("deleted", f.isDeleted()).
1632                                         put("version", f.getVersion()).
1633                                         put("content", f.getMimeType()).
1634                                         put("size", f.getFileSize()).
1635                                         put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1636                                         put("path", f.getFolder().getPath()).
1637                                         put("uri", getApiRoot() + f.getURI());
1638
1639                                 files.add(j);
1640                 }
1641                 json.put("files", files);
1642                 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1643                 json.put("permissions", renderJson(perms));
1644                 } catch (JSONException e) {
1645                         throw new ServletException(e);
1646                 } catch (ObjectNotFoundException e) {
1647                         throw new ServletException(e);
1648                 } catch (RpcException e) {
1649                         throw new ServletException(e);
1650                 }
1651
1652         // Prepare a writer to a buffered area
1653         ByteArrayOutputStream stream = new ByteArrayOutputStream();
1654         OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1655         PrintWriter writer = new PrintWriter(osWriter);
1656
1657         // Return an input stream to the underlying bytes
1658         writer.write(json.toString());
1659         writer.flush();
1660         return new ByteArrayInputStream(stream.toByteArray());
1661     }
1662
1663         /**
1664      * Return a String with a JSON representation of the metadata
1665      * of the specified file. If an old file body is provided, then
1666      * the metadata of that particular version will be returned.
1667      *
1668          * @param user the user that made the request
1669      * @param file the specified file header
1670      * @param oldBody the version number
1671      * @return the JSON-encoded file
1672      * @throws ServletException
1673          * @throws InsufficientPermissionsException if the user does not have
1674          *                      the necessary privileges to read the directory
1675      */
1676     private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
1677                 throws ServletException, InsufficientPermissionsException {
1678         JSONObject json = new JSONObject();
1679         try {
1680                 // Need to encode file name in order to properly display it in the web client.
1681                         json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
1682                                         put("owner", file.getOwner().getUsername()).
1683                                         put("versioned", file.isVersioned()).
1684                                         put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
1685                                         put("readForAll", file.isReadForAll()).
1686                                         put("tags", renderJson(file.getTags())).
1687                                         put("path", file.getFolder().getPath()).
1688                                 put("uri", getApiRoot() + file.getURI()).
1689                                         put("deleted", file.isDeleted());
1690                         JSONObject j = new JSONObject();
1691                         j.put("uri", getApiRoot() + file.getFolder().getURI()).
1692                                         put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
1693                         json.put("folder", j);
1694                         if (oldBody != null)
1695                                 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
1696                                                 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
1697                                                 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
1698                                                 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
1699                                                 put("content", oldBody.getMimeType()).
1700                                                 put("size", oldBody.getFileSize());
1701                         else
1702                                 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
1703                                                 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
1704                                                 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
1705                                                 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
1706                                                 put("content", file.getMimeType()).
1707                                                 put("size", file.getFileSize());
1708                 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
1709                 json.put("permissions", renderJson(perms));
1710                 } catch (JSONException e) {
1711                         throw new ServletException(e);
1712                 } catch (ObjectNotFoundException e) {
1713                         throw new ServletException(e);
1714                 } catch (RpcException e) {
1715                         throw new ServletException(e);
1716                 } catch (UnsupportedEncodingException e) {
1717                         throw new ServletException(e);
1718                 }
1719
1720         return json.toString();
1721     }
1722
1723         /**
1724          * Return a String with a JSON representation of the
1725          * specified set of permissions.
1726      *
1727          * @param permissions the set of permissions
1728          * @return the JSON-encoded object
1729          * @throws JSONException
1730          * @throws UnsupportedEncodingException
1731          */
1732         private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
1733                 JSONArray perms = new JSONArray();
1734                 for (PermissionDTO p: permissions) {
1735                         JSONObject permission = new JSONObject();
1736                         permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
1737                         if (p.getUser() != null)
1738                                 permission.put("user", p.getUser().getUsername());
1739                         if (p.getGroup() != null)
1740                                 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
1741                         perms.put(permission);
1742                 }
1743                 return perms;
1744         }
1745
1746         /**
1747          * Return a String with a JSON representation of the
1748          * specified collection of tags.
1749      *
1750          * @param tags the collection of tags
1751          * @return the JSON-encoded object
1752          * @throws JSONException
1753          * @throws UnsupportedEncodingException
1754          */
1755         private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
1756                 JSONArray tagArray = new JSONArray();
1757                 for (String t: tags)
1758                         tagArray.put(URLEncoder.encode(t,"UTF-8"));
1759                 return tagArray;
1760         }
1761
1762         /**
1763          * Retrieves the user who owns the destination namespace, for a
1764          * copy or move request.
1765          *
1766          * @param req the HTTP request
1767          * @return the owner of the namespace
1768          */
1769         protected User getDestinationOwner(HttpServletRequest req) {
1770                 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
1771         }
1772
1773         /**
1774          * A helper inner class for updating the progress status of a file upload.
1775          *
1776          * @author kman
1777          */
1778         public static class StatusProgressListener implements ProgressListener {
1779                 private int percentLogged = 0;
1780                 private long bytesTransferred = 0;
1781
1782                 private long fileSize = -100;
1783
1784                 private long tenKBRead = -1;
1785
1786                 private Long userId;
1787
1788                 private String filename;
1789
1790                 private ExternalAPI service;
1791
1792                 public StatusProgressListener(ExternalAPI aService) {
1793                         service = aService;
1794                 }
1795
1796                 /**
1797                  * Modify the userId.
1798                  *
1799                  * @param aUserId the userId to set
1800                  */
1801                 public void setUserId(Long aUserId) {
1802                         userId = aUserId;
1803                 }
1804
1805                 /**
1806                  * Modify the filename.
1807                  *
1808                  * @param aFilename the filename to set
1809                  */
1810                 public void setFilename(String aFilename) {
1811                         filename = aFilename;
1812                 }
1813
1814                 public void update(long bytesRead, long contentLength, int items) {
1815                         //monitoring per percent of bytes uploaded
1816                         bytesTransferred = bytesRead;
1817                         if (fileSize != contentLength)
1818                                 fileSize = contentLength;
1819                         int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
1820
1821                         if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
1822                                 if (percent != percentLogged){
1823                                         percentLogged = percent;
1824                                         try {
1825                                                 if (userId != null && filename != null)
1826                                                         service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
1827                                         } catch (ObjectNotFoundException e) {
1828                                                 // Swallow the exception since it is going to be caught
1829                                                 // by previously called methods
1830                                         }
1831                                 }
1832                 }
1833         }
1834 }