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