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