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