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