Statistics
| Branch: | Tag: | Revision:

root / src / gr / ebs / gss / server / rest / FilesHandler.java @ 77dcb3f1

History | View | Annotate | Download (68.7 kB)

1
/*
2
 * Copyright 2008, 2009 Electronic Business Systems Ltd.
3
 *
4
 * This file is part of GSS.
5
 *
6
 * GSS is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * GSS is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with GSS.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
package gr.ebs.gss.server.rest;
20

    
21
import gr.ebs.gss.client.exceptions.DuplicateNameException;
22
import gr.ebs.gss.client.exceptions.GSSIOException;
23
import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
24
import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
25
import gr.ebs.gss.client.exceptions.QuotaExceededException;
26
import gr.ebs.gss.client.exceptions.RpcException;
27
import gr.ebs.gss.server.domain.FileUploadStatus;
28
import gr.ebs.gss.server.domain.User;
29
import gr.ebs.gss.server.domain.dto.FileBodyDTO;
30
import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
31
import gr.ebs.gss.server.domain.dto.FolderDTO;
32
import gr.ebs.gss.server.domain.dto.GroupDTO;
33
import gr.ebs.gss.server.domain.dto.PermissionDTO;
34
import gr.ebs.gss.server.ejb.ExternalAPI;
35
import gr.ebs.gss.server.webdav.Range;
36

    
37
import java.io.BufferedReader;
38
import java.io.ByteArrayInputStream;
39
import java.io.ByteArrayOutputStream;
40
import java.io.File;
41
import java.io.FileInputStream;
42
import java.io.FileNotFoundException;
43
import java.io.IOException;
44
import java.io.InputStream;
45
import java.io.InputStreamReader;
46
import java.io.OutputStreamWriter;
47
import java.io.PrintWriter;
48
import java.io.UnsupportedEncodingException;
49
import java.net.URI;
50
import java.net.URISyntaxException;
51
import java.net.URLDecoder;
52
import java.net.URLEncoder;
53
import java.util.ArrayList;
54
import java.util.Collection;
55
import java.util.Date;
56
import java.util.HashSet;
57
import java.util.List;
58
import java.util.Set;
59
import java.util.StringTokenizer;
60
import java.util.concurrent.Callable;
61

    
62
import javax.servlet.ServletContext;
63
import javax.servlet.ServletException;
64
import javax.servlet.ServletOutputStream;
65
import javax.servlet.http.HttpServletRequest;
66
import javax.servlet.http.HttpServletResponse;
67

    
68
import org.apache.commons.fileupload.FileItemIterator;
69
import org.apache.commons.fileupload.FileItemStream;
70
import org.apache.commons.fileupload.FileUploadException;
71
import org.apache.commons.fileupload.ProgressListener;
72
import org.apache.commons.fileupload.servlet.ServletFileUpload;
73
import org.apache.commons.fileupload.util.Streams;
74
import org.apache.commons.httpclient.util.DateParseException;
75
import org.apache.commons.httpclient.util.DateUtil;
76
import org.apache.commons.logging.Log;
77
import org.apache.commons.logging.LogFactory;
78
import org.json.JSONArray;
79
import org.json.JSONException;
80
import org.json.JSONObject;
81

    
82

    
83
/**
84
 * A class that handles operations on the 'files' namespace.
85
 *
86
 * @author past
87
 */
88
public class FilesHandler extends RequestHandler {
89
        /**
90
         * The request parameter name for fetching a different version.
91
         */
92
        private static final String VERSION_PARAM = "version";
93

    
94
        /**
95
         * The request attribute containing the owner of the destination URI
96
         * in a copy or move request.
97
         */
98
        private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
99

    
100
        private static final int TRACK_PROGRESS_PERCENT = 5;
101

    
102
        /**
103
         * The form parameter name that contains the signature in a browser POST upload.
104
         */
105
        private static final String AUTHORIZATION_PARAMETER = "Authorization";
106

    
107
        /**
108
         * The form parameter name that contains the date in a browser POST upload.
109
         */
110
        private static final String DATE_PARAMETER = "Date";
111

    
112
        /**
113
         * The request parameter name for making an upload progress request.
114
         */
115
        private static final String PROGRESS_PARAMETER = "progress";
116

    
117
        /**
118
         * The request parameter name for restoring a previous version of a file.
119
         */
120
        private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
121

    
122
        /**
123
         * The logger.
124
         */
125
        private static Log logger = LogFactory.getLog(FilesHandler.class);
126

    
127
        /**
128
         * The servlet context provided by the call site.
129
         */
130
        private ServletContext context;
131

    
132
        /**
133
         * @param servletContext
134
         */
135
        public FilesHandler(ServletContext servletContext) {
136
                context = servletContext;
137
        }
138

    
139
        /**
140
     * Serve the specified resource, optionally including the data content.
141
     *
142
     * @param req The servlet request we are processing
143
     * @param resp The servlet response we are creating
144
     * @param content Should the content be included?
145
     *
146
     * @exception IOException if an input/output error occurs
147
     * @exception ServletException if a servlet-specified error occurs
148
     * @throws RpcException
149
     * @throws InsufficientPermissionsException
150
     * @throws ObjectNotFoundException
151
     */
152
        @Override
153
        protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
154
                    throws IOException, ServletException {
155
                boolean authDeferred = getAuthDeferred(req);
156
        String path = getInnerPath(req, PATH_FILES);
157
                if (path.equals(""))
158
                        path = "/";
159
                try {
160
                        path = URLDecoder.decode(path, "UTF-8");
161
                } catch (IllegalArgumentException e) {
162
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
163
                        return;
164
                }
165
            String progress = req.getParameter(PROGRESS_PARAMETER);
166

    
167
            if (logger.isDebugEnabled())
168
                        if (content)
169
                            logger.debug("Serving resource '" +        path + "' headers and data");
170
                    else
171
                            logger.debug("Serving resource '" +        path + "' headers only");
172

    
173
            User user = getUser(req);
174
            User owner = getOwner(req);
175
            if (user == null) user = owner;
176
        boolean exists = true;
177
        Object resource = null;
178
        FileHeaderDTO file = null;
179
        FolderDTO folder = null;
180
        try {
181
                resource = getService().getResourceAtPath(owner.getId(), path, false);
182
        } catch (ObjectNotFoundException e) {
183
            exists = false;
184
        } catch (RpcException e) {
185
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
186
                        return;
187
                }
188

    
189
            if (!exists) {
190
                        if (authDeferred) {
191
                                // We do not want to leak information if the request
192
                                // was not authenticated.
193
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
194
                                return;
195
                        }
196
                    // A request for upload progress.
197
                    if (progress != null && content) {
198
                            serveProgress(req, resp, progress, user, null);
199
                                return;
200
                    }
201

    
202
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
203
                    return;
204
            }
205

    
206
            if (resource instanceof FolderDTO)
207
                    folder = (FolderDTO) resource;
208
            else
209
                    file = (FileHeaderDTO) resource;
210

    
211
            // Now it's time to perform the deferred authentication check.
212
                // Since regular signature checking was already performed,
213
                // we need to check the read-all flag or the signature-in-parameters.
214
                if (authDeferred)
215
                        if (file != null && !file.isReadForAll() && content) {
216
                                // Check for GET with the signature in the request parameters.
217
                                String auth = req.getParameter(AUTHORIZATION_PARAMETER);
218
                                String dateParam = req.getParameter(DATE_PARAMETER);
219
                                if (auth == null || dateParam == null) {
220
                                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
221
                                        return;
222
                                }
223

    
224
                            long timestamp;
225
                                try {
226
                                        timestamp = DateUtil.parseDate(dateParam).getTime();
227
                                } catch (DateParseException e) {
228
                                    resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
229
                                    return;
230
                                }
231
                            if (!isTimeValid(timestamp)) {
232
                                    resp.sendError(HttpServletResponse.SC_FORBIDDEN);
233
                                    return;
234
                            }
235

    
236
                                // Fetch the Authorization parameter and find the user specified in it.
237
                                String[] authParts = auth.split(" ");
238
                                if (authParts.length != 2) {
239
                                    resp.sendError(HttpServletResponse.SC_FORBIDDEN);
240
                                    return;
241
                            }
242
                                String username = authParts[0];
243
                                String signature = authParts[1];
244
                                user = null;
245
                                try {
246
                                        user = getService().findUser(username);
247
                                } catch (RpcException e) {
248
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
249
                                        return;
250
                                }
251
                                if (user == null) {
252
                                    resp.sendError(HttpServletResponse.SC_FORBIDDEN);
253
                                    return;
254
                            }
255
                                req.setAttribute(USER_ATTRIBUTE, user);
256

    
257
                                // Remove the servlet path from the request URI.
258
                                String p = req.getRequestURI();
259
                                String servletPath = req.getContextPath() + req.getServletPath();
260
                                p = p.substring(servletPath.length());
261
                                // Validate the signature in the Authorization parameter.
262
                                String data = req.getMethod() + dateParam + p;
263
                                if (!isSignatureValid(signature, user, data)) {
264
                                    resp.sendError(HttpServletResponse.SC_FORBIDDEN);
265
                                    return;
266
                            }
267
                        } else if (file != null && !file.isReadForAll() || file == null) {
268
                                // Check for a read-for-all file request.
269
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
270
                                return;
271
                        }
272

    
273
            // If the resource is not a collection, and the resource path
274
            // ends with "/" or "\", return NOT FOUND.
275
            if (folder == null)
276
                        if (path.endsWith("/") || path.endsWith("\\")) {
277
                            resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
278
                            return;
279
                    }
280

    
281
            // Workaround for IE's broken caching behavior.
282
            if (folder != null)
283
                    resp.setHeader("Expires", "-1");
284

    
285
            // A request for upload progress.
286
            if (progress != null && content) {
287
                    if (file == null) {
288
                            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
289
                            return;
290
                    }
291
                    serveProgress(req, resp, progress, user, file);
292
                        return;
293
            }
294

    
295
                // Fetch the version to retrieve, if specified.
296
                String verStr = req.getParameter(VERSION_PARAM);
297
                int version = 0;
298
                FileBodyDTO oldBody = null;
299
                if (verStr != null && file != null)
300
                        try {
301
                                version = Integer.valueOf(verStr);
302
                        } catch (NumberFormatException e) {
303
                                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
304
                            return;
305
                        }
306
                if (version > 0)
307
                        try {
308
                                oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
309
                        } catch (RpcException e) {
310
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
311
                                return;
312
                        } catch (ObjectNotFoundException e) {
313
                            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
314
                            return;
315
                        } catch (InsufficientPermissionsException e) {
316
                            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
317
                            return;
318
                        }
319

    
320
            // Check if the conditions specified in the optional If headers are
321
            // satisfied. Doing this for folders would require recursive checking
322
            // for all of their children, which in turn would defy the purpose of
323
            // the optimization.
324
            if (folder == null)
325
                        // Checking If headers.
326
                    if (!checkIfHeaders(req, resp, file, oldBody))
327
                                return;
328

    
329
            // Find content type.
330
            String contentType = null;
331
            if (file != null) {
332
                contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
333
                if (contentType == null) {
334
                        contentType = context.getMimeType(file.getName());
335
                        file.setMimeType(contentType);
336
                }
337
            } else
338
                        contentType = "application/json;charset=UTF-8";
339

    
340
            ArrayList ranges = null;
341
            long contentLength = -1L;
342

    
343
            if (file != null) {
344
                    // Parse range specifier.
345
                    ranges = parseRange(req, resp, file, oldBody);
346
                    // ETag header
347
                    resp.setHeader("ETag", getETag(file, oldBody));
348
                    // Last-Modified header.
349
                    String lastModified = oldBody == null ?
350
                                            getLastModifiedHttp(file.getAuditInfo()) :
351
                                            getLastModifiedHttp(oldBody.getAuditInfo());
352
                    resp.setHeader("Last-Modified", lastModified);
353
                    // X-GSS-Metadata header.
354
                    try {
355
                                resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
356
                        } catch (InsufficientPermissionsException e) {
357
                                resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
358
                        return;
359
                }
360
                    // Get content length.
361
                    contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
362
                    // Special case for zero length files, which would cause a
363
                    // (silent) ISE when setting the output buffer size.
364
                    if (contentLength == 0L)
365
                                content = false;
366
            } else
367
                    // Set the folder X-GSS-Metadata header.
368
                    try {
369
                                resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
370
                        } catch (InsufficientPermissionsException e) {
371
                                resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
372
                        return;
373
                }
374

    
375
            ServletOutputStream ostream = null;
376
            PrintWriter writer = null;
377

    
378
            if (content)
379
                        try {
380
                            ostream = resp.getOutputStream();
381
                    } catch (IllegalStateException e) {
382
                            // If it fails, we try to get a Writer instead if we're
383
                            // trying to serve a text file
384
                            if ( contentType == null
385
                                                    || contentType.startsWith("text")
386
                                                    || contentType.endsWith("xml") )
387
                                        writer = resp.getWriter();
388
                                else
389
                                        throw e;
390
                    }
391

    
392
            if (folder != null
393
                                    || (ranges == null || ranges.isEmpty())
394
                                                            && req.getHeader("Range") == null
395
                                                            || ranges == FULL) {
396
                    // Set the appropriate output headers
397
                    if (contentType != null) {
398
                            if (logger.isDebugEnabled())
399
                                    logger.debug("contentType='" + contentType + "'");
400
                            resp.setContentType(contentType);
401
                    }
402
                    if (file != null && contentLength >= 0) {
403
                            if (logger.isDebugEnabled())
404
                                    logger.debug("contentLength=" + contentLength);
405
                            if (contentLength < Integer.MAX_VALUE)
406
                                        resp.setContentLength((int) contentLength);
407
                                else
408
                                        // Set the content-length as String to be able to use a long
409
                                    resp.setHeader("content-length", "" + contentLength);
410
                    }
411

    
412
                    InputStream renderResult = null;
413
                    if (folder != null)
414
                                if (content)
415
                                        // Serve the directory browser
416
                                    try {
417
                                                renderResult = renderJson(user, folder);
418
                                        } catch (InsufficientPermissionsException e) {
419
                                                resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
420
                                        return;
421
                                        }
422
                    // Copy the input stream to our output stream (if requested)
423
                    if (content) {
424
                            try {
425
                                    resp.setBufferSize(output);
426
                            } catch (IllegalStateException e) {
427
                                    // Silent catch
428
                            }
429
                            try {
430
                                    if(file != null)
431
                                                if (needsContentDisposition(req))
432
                                                    resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+URLEncoder.encode(file.getName(),"UTF-8"));
433
                                            else
434
                                                    resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+URLEncoder.encode(file.getName(),"UTF-8"));
435
                                    if (ostream != null)
436
                                                copy(file, renderResult, ostream, req, oldBody);
437
                                        else
438
                                                copy(file, renderResult, writer, req, oldBody);
439
                                    if (file!=null) getService().updateAccounting(owner, new Date(), contentLength);
440
                        } catch (ObjectNotFoundException e) {
441
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
442
                                return;
443
                        } catch (InsufficientPermissionsException e) {
444
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
445
                                return;
446
                        } catch (RpcException e) {
447
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
448
                                return;
449
                        }
450
                    }
451
            } else {
452
                    if (ranges == null || ranges.isEmpty())
453
                            return;
454
                    // Partial content response.
455
                    resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
456

    
457
                    if (ranges.size() == 1) {
458
                            Range range = (Range) ranges.get(0);
459
                            resp.addHeader("Content-Range", "bytes "
460
                                                    + range.start
461
                                                    + "-" + range.end + "/"
462
                                                    + range.length);
463
                            long length = range.end - range.start + 1;
464
                            if (length < Integer.MAX_VALUE)
465
                                        resp.setContentLength((int) length);
466
                                else
467
                                        // Set the content-length as String to be able to use a long
468
                                    resp.setHeader("content-length", "" + length);
469

    
470
                            if (contentType != null) {
471
                                    if (logger.isDebugEnabled())
472
                                            logger.debug("contentType='" + contentType + "'");
473
                                    resp.setContentType(contentType);
474
                            }
475

    
476
                            if (content) {
477
                                    try {
478
                                            resp.setBufferSize(output);
479
                                    } catch (IllegalStateException e) {
480
                                            // Silent catch
481
                                    }
482
                                    try {
483
                                            if (ostream != null)
484
                                                        copy(file, ostream, range, req, oldBody);
485
                                                else
486
                                                        copy(file, writer, range, req, oldBody);
487
                                            getService().updateAccounting(owner, new Date(), contentLength);
488
                                } catch (ObjectNotFoundException e) {
489
                                        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
490
                                        return;
491
                                } catch (InsufficientPermissionsException e) {
492
                                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
493
                                        return;
494
                                } catch (RpcException e) {
495
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
496
                                        return;
497
                                }
498
                            }
499
                    } else {
500
                            resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
501
                            if (content) {
502
                                    try {
503
                                            resp.setBufferSize(output);
504
                                    } catch (IllegalStateException e) {
505
                                            // Silent catch
506
                                    }
507
                                    try {
508
                                            if (ostream != null)
509
                                                        copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
510
                                                else
511
                                                        copy(file, writer, ranges.iterator(), contentType, req, oldBody);
512
                                            getService().updateAccounting(owner, new Date(), contentLength);
513
                                } catch (ObjectNotFoundException e) {
514
                                        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
515
                                        return;
516
                                } catch (InsufficientPermissionsException e) {
517
                                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
518
                                        return;
519
                                } catch (RpcException e) {
520
                                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
521
                                        return;
522
                                }
523
                            }
524
                    }
525
            }
526
    }
527

    
528
        /**
529
         * Determines whether the user agent needs the Content-Disposition
530
         * header to be set, in order to properly download a file.
531
         *
532
         * @param req the HTTP request
533
         * @return true if the Content-Disposition HTTP header must be set
534
         */
535
        private boolean needsContentDisposition(HttpServletRequest req) {
536
                /*String agent = req.getHeader("user-agent");
537
                if (agent != null && agent.contains("MSIE"))
538
                        return true;*/
539
                String dl = req.getParameter("dl");
540
                if ("1".equals(dl))
541
                        return true;
542
                return false;
543
        }
544

    
545
        /**
546
         * Sends a progress update on the amount of bytes received until now for
547
         * a file that the current user is currently uploading.
548
         *
549
         * @param req the HTTP request
550
         * @param resp the HTTP response
551
         * @param parameter the value for the progress request parameter
552
         * @param user the current user
553
         * @param file the file being uploaded, or null if the request is about a new file
554
         * @throws IOException if an I/O error occurs
555
         */
556
        private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
557
                                String parameter, User user, FileHeaderDTO file)        throws IOException {
558
                String filename = file == null ? parameter : file.getName();
559
                try {
560
                        FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
561
                        if (status == null) {
562
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
563
                                return;
564
                        }
565
                        JSONObject json = new JSONObject();
566
                        json.put("bytesUploaded", status.getBytesUploaded()).
567
                                put("bytesTotal", status.getFileSize());
568
                        sendJson(req, resp, json.toString());
569

    
570
                        // Workaround for IE's broken caching behavior.
571
                    resp.setHeader("Expires", "-1");
572
                        return;
573
                } catch (RpcException e) {
574
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
575
                        return;
576
                } catch (JSONException e) {
577
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
578
                        return;
579
                }
580
        }
581

    
582
        /**
583
         * Server a POST request to create/modify a file or folder.
584
         *
585
         * @param req the HTTP request
586
         * @param resp the HTTP response
587
     * @exception IOException if an input/output error occurs
588
         */
589
        void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
590
                boolean authDeferred = getAuthDeferred(req);
591
            if (!authDeferred && req.getParameterMap().size() > 1) {
592
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
593
                    return;
594
            }
595
        String path = getInnerPath(req, PATH_FILES);
596
            path = path.endsWith("/")? path: path + '/';
597
                try {
598
                    path = URLDecoder.decode(path, "UTF-8");
599
                } catch (IllegalArgumentException e) {
600
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
601
                        return;
602
                }
603
            // We only defer authenticating multipart POST requests.
604
            if (authDeferred) {
605
                        if (!ServletFileUpload.isMultipartContent(req)) {
606
                            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
607
                            return;
608
                    }
609
                        handleMultipart(req, resp, path);
610
                        return;
611
                }
612

    
613
            String newName = req.getParameter(NEW_FOLDER_PARAMETER);
614
            boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
615
            boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
616
            boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
617
            String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
618
            String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
619
            String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
620

    
621
            if (newName != null)
622
                        createFolder(req, resp, path, newName);
623
            else if (hasUpdateParam)
624
                        updateResource(req, resp, path);
625
                else if (hasTrashParam)
626
                        trashResource(req, resp, path);
627
                else if (hasRestoreParam)
628
                        restoreResource(req, resp, path);
629
                else if (copyTo != null)
630
                        copyResource(req, resp, path, copyTo);
631
                else if (moveTo != null)
632
                        moveResource(req, resp, path, moveTo);
633
                else if (restoreVersion != null)
634
                        restoreVersion(req, resp, path, restoreVersion);
635
                else
636
                        // IE with Gears uses POST for multiple uploads.
637
                        putResource(req, resp);
638
        }
639

    
640
        /**
641
         * Restores a previous version for a file.
642
         *
643
         * @param req the HTTP request
644
         * @param resp the HTTP response
645
         * @param path the resource path
646
         * @param version the version number to restore
647
         * @throws IOException if an I/O error occurs
648
         */
649
        private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
650
                User user = getUser(req);
651
                User owner = getOwner(req);
652
                Object resource = null;
653
                try {
654
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
655
                } catch (ObjectNotFoundException e) {
656
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
657
                        return;
658
                } catch (RpcException e) {
659
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
660
                        return;
661
                }
662
                if (resource instanceof FolderDTO) {
663
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
664
                        return;
665
                }
666

    
667
                try {
668
                        FileHeaderDTO file = (FileHeaderDTO) resource;
669
                        int oldVersion = Integer.parseInt(version);
670
                        getService().restoreVersion(user.getId(), file.getId(), oldVersion);
671
                } catch (InsufficientPermissionsException e) {
672
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
673
                } catch (ObjectNotFoundException e) {
674
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
675
                } catch (RpcException e) {
676
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
677
                } catch (GSSIOException e) {
678
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
679
                } catch (QuotaExceededException e) {
680
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
681
                } catch (NumberFormatException e) {
682
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
683
                }
684
        }
685

    
686
        /**
687
         * A method for handling multipart POST requests for uploading
688
         * files from browser-based JavaScript clients.
689
         *
690
         * @param request the HTTP request
691
         * @param response the HTTP response
692
         * @param path the resource path
693
         * @throws IOException in case an error occurs writing to the
694
         *                 response stream
695
         */
696
        private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
697
            if (logger.isDebugEnabled())
698
                           logger.debug("Multipart POST for resource: " + path);
699

    
700
            User owner = getOwner(request);
701
            boolean exists = true;
702
        Object resource = null;
703
        FileHeaderDTO file = null;
704
        try {
705
                resource = getService().getResourceAtPath(owner.getId(), path, false);
706
        } catch (ObjectNotFoundException e) {
707
            exists = false;
708
        } catch (RpcException e) {
709
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
710
                        return;
711
                }
712

    
713
        if (exists)
714
                        if (resource instanceof FileHeaderDTO) {
715
                            file = (FileHeaderDTO) resource;
716
                            if (file.isDeleted()) {
717
                                    response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
718
                                return;
719
                            }
720
                        } else {
721
                        response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
722
                            return;
723
                }
724

    
725
        FolderDTO folder = null;
726
            Object parent;
727
            String parentPath = null;
728
                try {
729
                        parentPath = getParentPath(path);
730
                        parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
731
                } catch (ObjectNotFoundException e) {
732
                    response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
733
                    return;
734
                } catch (RpcException e) {
735
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
736
                        return;
737
                }
738
            if (!(parent instanceof FolderDTO)) {
739
                    response.sendError(HttpServletResponse.SC_CONFLICT);
740
                    return;
741
            }
742
                   folder = (FolderDTO) parent;
743
            String fileName = getLastElement(path);
744

    
745
                FileItemIterator iter;
746
                File uploadedFile = null;
747
                try {
748
                        // Create a new file upload handler.
749
                        ServletFileUpload upload = new ServletFileUpload();
750
                        StatusProgressListener progressListener = new StatusProgressListener(getService());
751
                        upload.setProgressListener(progressListener);
752
                        iter = upload.getItemIterator(request);
753
                        String dateParam = null;
754
                        String auth = null;
755
                        while (iter.hasNext()) {
756
                                FileItemStream item = iter.next();
757
                                String name = item.getFieldName();
758
                                InputStream stream = item.openStream();
759
                                if (item.isFormField()) {
760
                                        final String value = Streams.asString(stream);
761
                                        if (name.equals(DATE_PARAMETER))
762
                                                dateParam = value;
763
                                        else if (name.equals(AUTHORIZATION_PARAMETER))
764
                                                auth = value;
765

    
766
                                        if (logger.isDebugEnabled())
767
                                                logger.debug(name + ":" + value);
768
                                } else {
769
                                        // Fetch the timestamp used to guard against replay attacks.
770
                                    if (dateParam == null) {
771
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
772
                                            return;
773
                                    }
774

    
775
                                    long timestamp;
776
                                        try {
777
                                                timestamp = DateUtil.parseDate(dateParam).getTime();
778
                                        } catch (DateParseException e) {
779
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
780
                                            return;
781
                                        }
782
                                    if (!isTimeValid(timestamp)) {
783
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
784
                                            return;
785
                                    }
786

    
787
                                        // Fetch the Authorization parameter and find the user specified in it.
788
                                    if (auth == null) {
789
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
790
                                            return;
791
                                    }
792
                                        String[] authParts = auth.split(" ");
793
                                        if (authParts.length != 2) {
794
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
795
                                            return;
796
                                    }
797
                                        String username = authParts[0];
798
                                        String signature = authParts[1];
799
                                        User user = null;
800
                                        try {
801
                                                user = getService().findUser(username);
802
                                        } catch (RpcException e) {
803
                                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
804
                                                return;
805
                                        }
806
                                        if (user == null) {
807
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
808
                                            return;
809
                                    }
810
                                        request.setAttribute(USER_ATTRIBUTE, user);
811

    
812
                                        // Remove the servlet path from the request URI.
813
                                        String p = request.getRequestURI();
814
                                        String servletPath = request.getContextPath() + request.getServletPath();
815
                                        p = p.substring(servletPath.length());
816
                                        // Validate the signature in the Authorization parameter.
817
                                        String data = request.getMethod() + dateParam + p;
818
                                        if (!isSignatureValid(signature, user, data)) {
819
                                            response.sendError(HttpServletResponse.SC_FORBIDDEN);
820
                                            return;
821
                                    }
822

    
823
                                        progressListener.setUserId(user.getId());
824
                                        progressListener.setFilename(fileName);
825
                                        String contentType = item.getContentType();
826

    
827
                                        try {
828
                                                uploadedFile = getService().uploadFile(stream, user.getId());
829
                                        } catch (IOException ex) {
830
                                                throw new GSSIOException(ex, false);
831
                                        }
832
                                        FileHeaderDTO fileDTO = null;
833
                                        if (file == null)
834
                                                fileDTO = getService().createFile(user.getId(), folder.getId(), fileName, contentType, uploadedFile.getCanonicalFile().length(), uploadedFile.getAbsolutePath());
835
                                        else
836
                                                fileDTO = getService().updateFileContents(user.getId(), file.getId(), contentType, uploadedFile.getCanonicalFile().length(), uploadedFile.getAbsolutePath());
837
                                        getService().updateAccounting(owner, new Date(), fileDTO.getFileSize());
838
                                        getService().removeFileUploadProgress(user.getId(), fileName);
839
                                }
840
                        }
841
                        // We can't return 204 here since GWT's onSubmitComplete won't fire.
842
                        response.setContentType("text/html");
843
            response.getWriter().print("<pre></pre>");
844
                } catch (FileUploadException e) {
845
                        String error = "Error while uploading file";
846
                        logger.error(error, e);
847
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
848
                } catch (GSSIOException e) {
849
                        if (uploadedFile != null && uploadedFile.exists())
850
                                uploadedFile.delete();
851
                        String error = "Error while uploading file";
852
                        if (e.logAsError())
853
                                logger.error(error, e);
854
                        else
855
                                logger.debug(error, e);
856
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
857
                } catch (DuplicateNameException e) {
858
                        if (uploadedFile != null && uploadedFile.exists())
859
                                uploadedFile.delete();
860
                        String error = "The specified file name already exists in this folder";
861
                        logger.error(error, e);
862
                        response.sendError(HttpServletResponse.SC_CONFLICT, error);
863

    
864
                } catch (InsufficientPermissionsException e) {
865
                        if (uploadedFile != null && uploadedFile.exists())
866
                                uploadedFile.delete();
867
                        String error = "You don't have the necessary permissions";
868
                        logger.error(error, e);
869
                        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
870

    
871
                } catch (QuotaExceededException e) {
872
                        if (uploadedFile != null && uploadedFile.exists())
873
                                uploadedFile.delete();
874
                        String error = "Not enough free space available";
875
                        if (logger.isDebugEnabled())
876
                                logger.debug(error, e);
877
                        response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
878

    
879
                } catch (ObjectNotFoundException e) {
880
                        if (uploadedFile != null && uploadedFile.exists())
881
                                uploadedFile.delete();
882
                        String error = "A specified object was not found";
883
                        logger.error(error, e);
884
                        response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
885
                } catch (RpcException e) {
886
                        if (uploadedFile != null && uploadedFile.exists())
887
                                uploadedFile.delete();
888
                        String error = "An error occurred while communicating with the service";
889
                        logger.error(error, e);
890
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
891
                }
892
        }
893

    
894
        private String getBackupFilename(FileHeaderDTO file, String filename){
895
                List<FileHeaderDTO> deletedFiles = new ArrayList<FileHeaderDTO>();
896
                try{
897
                        deletedFiles = getService().getDeletedFiles(file.getOwner().getId());
898
                }
899
                catch(ObjectNotFoundException e){
900

    
901
                } catch (RpcException e) {
902

    
903
                }
904
                List<FileHeaderDTO> filesInSameFolder = new ArrayList<FileHeaderDTO>();
905
                for(FileHeaderDTO deleted : deletedFiles)
906
                        if(deleted.getFolder().getId().equals(file.getFolder().getId()))
907
                                filesInSameFolder.add(deleted);
908
                int i=1;
909
                String filenameToCheck = filename;
910
                for(FileHeaderDTO same : filesInSameFolder)
911
                        if(same.getName().startsWith(filename)){
912
                                String toCheck=same.getName().substring(filename.length(),same.getName().length());
913
                                if(toCheck.startsWith(" ")){
914
                                        int test =-1;
915
                                        try{
916
                                                test = Integer.valueOf(toCheck.replace(" ",""));
917
                                        }
918
                                        catch(NumberFormatException e){
919
                                                //do nothing since string is not a number
920
                                        }
921
                                        if(test>=i)
922
                                                i = test+1;
923
                                }
924
                        }
925

    
926
                return filename+" "+i;
927
        }
928

    
929
        /**
930
         * Move the resource in the specified path to the specified destination.
931
         *
932
         * @param req the HTTP request
933
         * @param resp the HTTP response
934
         * @param path the path of the resource
935
         * @param moveTo the destination of the move procedure
936
         * @throws IOException if an input/output error occurs
937
         */
938
        private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
939
                User user = getUser(req);
940
                User owner = getOwner(req);
941
                Object resource = null;
942
                try {
943
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
944
                } catch (ObjectNotFoundException e) {
945
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
946
                        return;
947
                } catch (RpcException e) {
948
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
949
                        return;
950
                }
951

    
952
        String destination = null;
953
        User destOwner = null;
954
                boolean exists = true;
955
                try {
956
                        destination = getDestinationPath(req, encodePath(moveTo));
957
                        destination = URLDecoder.decode(destination, "UTF-8");
958
                        destOwner = getDestinationOwner(req);
959
                        getService().getResourceAtPath(destOwner.getId(), destination, true);
960
                } catch (ObjectNotFoundException e) {
961
                        exists = false;
962
                } catch (URISyntaxException e) {
963
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
964
                        return;
965
                } catch (RpcException e) {
966
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
967
                        return;
968
                }
969
                if (exists) {
970
                        resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
971
                        return;
972
                }
973

    
974
                try {
975
                        if (resource instanceof FolderDTO) {
976
                                FolderDTO folder = (FolderDTO) resource;
977
                                getService().moveFolderToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
978
                        } else {
979
                                FileHeaderDTO file = (FileHeaderDTO) resource;
980
                                getService().moveFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
981
                        }
982
                } catch (InsufficientPermissionsException e) {
983
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
984
                } catch (ObjectNotFoundException e) {
985
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
986
                } catch (RpcException e) {
987
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
988
                } catch (DuplicateNameException e) {
989
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
990
                } catch (GSSIOException e) {
991
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
992
                } catch (QuotaExceededException e) {
993
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
994
                }
995
        }
996

    
997
        /**
998
         * Copy the resource in the specified path to the specified destination.
999
         *
1000
         * @param req the HTTP request
1001
         * @param resp the HTTP response
1002
         * @param path the path of the resource
1003
         * @param copyTo the destination of the copy procedure
1004
         * @throws IOException if an input/output error occurs
1005
         */
1006
        private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1007
                User user = getUser(req);
1008
                User owner = getOwner(req);
1009
                Object resource = null;
1010
                try {
1011
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1012
                } catch (ObjectNotFoundException e) {
1013
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1014
                        return;
1015
                } catch (RpcException e) {
1016
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1017
                        return;
1018
                }
1019

    
1020
        String destination = null;
1021
        User destOwner = null;
1022
                boolean exists = true;
1023
                try {
1024
                        String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1025
                        destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1026
                        destOwner = getDestinationOwner(req);
1027
                        getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1028
                } catch (ObjectNotFoundException e) {
1029
                        exists = false;
1030
                } catch (URISyntaxException e) {
1031
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1032
                        return;
1033
                } catch (RpcException e) {
1034
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1035
                        return;
1036
                }
1037
                if (exists) {
1038
                        resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1039
                        return;
1040
                }
1041

    
1042
                try {
1043
                        if (resource instanceof FolderDTO) {
1044
                                FolderDTO folder = (FolderDTO) resource;
1045
                                getService().copyFolderStructureToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
1046
                        } else {
1047
                                FileHeaderDTO file = (FileHeaderDTO) resource;
1048
                                getService().copyFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
1049
                        }
1050
                } catch (InsufficientPermissionsException e) {
1051
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1052
                } catch (ObjectNotFoundException e) {
1053
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1054
                } catch (RpcException e) {
1055
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1056
                } catch (DuplicateNameException e) {
1057
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1058
                } catch (GSSIOException e) {
1059
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1060
                } catch (QuotaExceededException e) {
1061
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1062
                }
1063
        }
1064

    
1065
        private String encodePath(String path) throws UnsupportedEncodingException{
1066
                StringTokenizer str = new StringTokenizer(path, "/:", true);
1067
                String result = new String();
1068
                while(str.hasMoreTokens()){
1069
                        String token = str.nextToken();
1070
                        if(!token.equals("/") && !token.equals(":"))
1071
                                token = URLEncoder.encode(token,"UTF-8");
1072
                        result = result + token;
1073
                }
1074
                return result;
1075
        }
1076
        /**
1077
         * A helper method that extracts the relative resource path,
1078
         * after removing the 'files' namespace.
1079
         * The path returned is <i>not</i> URL-decoded.
1080
         *
1081
         * @param req the HTTP request
1082
         * @param path the specified path
1083
         * @return the path relative to the root folder
1084
         * @throws URISyntaxException
1085
         * @throws RpcException in case an error occurs while communicating
1086
         *                                                 with the backend
1087
         * @throws UnsupportedEncodingException
1088
         */
1089
        private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1090
                URI uri = new URI(path);
1091
                String dest = uri.getRawPath();
1092
                // Remove the context path from the destination URI.
1093
                String contextPath = req.getContextPath();
1094
                if (!dest.startsWith(contextPath))
1095
                        throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1096
                dest = dest.substring(contextPath.length());
1097
                // Remove the servlet path from the destination URI.
1098
                String servletPath = req.getServletPath();
1099
                if (!dest.startsWith(servletPath))
1100
                        throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1101
                dest = dest.substring(servletPath.length());
1102
            // Strip the username part
1103
                if (dest.length() < 2)
1104
                        throw new URISyntaxException(dest, "No username in the destination URI");
1105
                int slash = dest.substring(1).indexOf('/');
1106
                if (slash == -1)
1107
                        throw new URISyntaxException(dest, "No username in the destination URI");
1108
                // Decode the user to get the proper characters (mainly the @)
1109
                String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1110
                User o;
1111
                o = getService().findUser(owner);
1112
                if (o == null)
1113
                        throw new URISyntaxException(dest, "User " + owner + " not found");
1114

    
1115
                req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1116
                dest = dest.substring(slash + 1);
1117

    
1118
                // Chop the resource namespace part
1119
                dest = dest.substring(RequestHandler.PATH_FILES.length());
1120

    
1121
            dest = dest.endsWith("/")? dest: dest + '/';
1122
                return dest;
1123
        }
1124

    
1125
        /**
1126
         * Move the resource in the specified path to the trash bin.
1127
         *
1128
         * @param req the HTTP request
1129
         * @param resp the HTTP response
1130
         * @param path the path of the resource
1131
         * @throws IOException if an input/output error occurs
1132
         */
1133
        private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1134
                User user = getUser(req);
1135
                User owner = getOwner(req);
1136
                Object resource = null;
1137
                try {
1138
                        resource = getService().getResourceAtPath(owner.getId(), path, true);
1139
                } catch (ObjectNotFoundException e) {
1140
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1141
                        return;
1142
                } catch (RpcException e) {
1143
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1144
                        return;
1145
                }
1146

    
1147
                try {
1148
                        if (resource instanceof FolderDTO) {
1149
                                FolderDTO folder = (FolderDTO) resource;
1150
                                getService().moveFolderToTrash(user.getId(), folder.getId());
1151
                        } else {
1152
                                FileHeaderDTO file = (FileHeaderDTO) resource;
1153
                                getService().moveFileToTrash(user.getId(), file.getId());
1154
                        }
1155
                } catch (InsufficientPermissionsException e) {
1156
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1157
                } catch (ObjectNotFoundException e) {
1158
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1159
                } catch (RpcException e) {
1160
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1161
                }
1162
        }
1163

    
1164
        /**
1165
         * Restore the resource in the specified path from the trash bin.
1166
         *
1167
         * @param req the HTTP request
1168
         * @param resp the HTTP response
1169
         * @param path the path of the resource
1170
         * @throws IOException if an input/output error occurs
1171
         */
1172
        private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1173
                User user = getUser(req);
1174
                User owner = getOwner(req);
1175
                Object resource = null;
1176
                try {
1177
                        resource = getService().getResourceAtPath(owner.getId(), path, false);
1178
                } catch (ObjectNotFoundException e) {
1179
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1180
                        return;
1181
                } catch (RpcException e) {
1182
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1183
                        return;
1184
                }
1185

    
1186
                try {
1187
                        if (resource instanceof FolderDTO) {
1188
                                FolderDTO folder = (FolderDTO) resource;
1189
                                getService().removeFolderFromTrash(user.getId(), folder.getId());
1190
                        } else {
1191
                                FileHeaderDTO file = (FileHeaderDTO) resource;
1192
                                getService().removeFileFromTrash(user.getId(), file.getId());
1193
                        }
1194
                } catch (InsufficientPermissionsException e) {
1195
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1196
                } catch (ObjectNotFoundException e) {
1197
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1198
                } catch (RpcException e) {
1199
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1200
                }
1201
        }
1202

    
1203
        /**
1204
         * Update the resource in the specified path.
1205
         *
1206
         * @param req the HTTP request
1207
         * @param resp the HTTP response
1208
         * @param path the path of the resource
1209
         * @throws IOException if an input/output error occurs
1210
         */
1211
        private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1212
                final User user = getUser(req);
1213
                User owner = getOwner(req);
1214
                Object resource = null;
1215
                try {
1216
                        resource = getService().getResourceAtPath(owner.getId(), path, false);
1217
                } catch (ObjectNotFoundException e) {
1218
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1219
                        return;
1220
                } catch (RpcException e) {
1221
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1222
                        return;
1223
                }
1224
                //use utf-8 encoding for reading request
1225
                BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1226
                StringBuffer input = new StringBuffer();
1227
                String line = null;
1228
                JSONObject json = null;
1229
                while ((line = reader.readLine()) != null)
1230
                        input.append(line);
1231
                reader.close();
1232
                try {
1233
                        json = new JSONObject(input.toString());
1234
                        if (logger.isDebugEnabled())
1235
                                logger.debug("JSON update: " + json);
1236
                        if (resource instanceof FolderDTO) {
1237
                                final FolderDTO folder = (FolderDTO) resource;
1238
                                String name = json.optString("name");
1239
                                if (!name.isEmpty()){
1240
                                        try {
1241
                                                name = URLDecoder.decode(name, "UTF-8");
1242
                                        } catch (IllegalArgumentException e) {
1243
                                                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1244
                                                return;
1245
                                        }
1246
                                        final String fName = name;
1247
                                        FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1248
                                                @Override
1249
                                                public FolderDTO call() throws Exception {
1250
                                                        return getService().modifyFolder(user.getId(), folder.getId(), fName);
1251
                                                }
1252

    
1253
                                        });
1254
                                        String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1255
                                        String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1256
                                        parentUrl = parentUrl.replaceAll(fpath, "");
1257
                                        if(!parentUrl.endsWith("/"))
1258
                                                parentUrl = parentUrl+"/";
1259
                                        parentUrl = parentUrl+folderUpdated.getOwner().getUsername()+PATH_FILES+folderUpdated.getPath();
1260
                                        resp.getWriter().println(parentUrl);
1261
                                }
1262

    
1263
                                JSONArray permissions = json.optJSONArray("permissions");
1264
                                if (permissions != null) {
1265
                                        final Set<PermissionDTO> perms = parsePermissions(user, permissions);
1266
                                        new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1267
                                                @Override
1268
                                                public Object call() throws Exception {
1269
                                                        getService().setFolderPermissions(user.getId(), folder.getId(), perms);
1270
                                                        return null;
1271
                                                }
1272

    
1273
                                        });
1274
                                }
1275
                        } else {
1276
                                final FileHeaderDTO file = (FileHeaderDTO) resource;
1277
                                String name = null;
1278
                                if (json.opt("name") != null)
1279
                                        name = json.optString("name");
1280
                                JSONArray tagset = json.optJSONArray("tags");
1281
                                String tags= null;
1282
                                StringBuffer t = new StringBuffer();
1283
                                if (tagset != null) {
1284
                                        for (int i = 0; i < tagset.length(); i++)
1285
                                                t.append(tagset.getString(i) + ',');
1286
                                        tags = t.toString();
1287
                                }
1288
                                if (name != null || tags != null) {
1289
                                        final String fName = name;
1290
                                        final String fTags = tags;
1291
                                        new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1292
                                                @Override
1293
                                                public Object call() throws Exception {
1294
                                                        getService().updateFile(user.getId(), file.getId(), fName, fTags);
1295
                                                        return null;
1296
                                                }
1297

    
1298
                                        });
1299
                                }
1300

    
1301
                                JSONArray permissions = json.optJSONArray("permissions");
1302
                                Set<PermissionDTO> perms = null;
1303
                                if (permissions != null)
1304
                                        perms = parsePermissions(user, permissions);
1305
                                Boolean readForAll = null;
1306
                                if (json.opt("readForAll") != null)
1307
                                        readForAll = json.optBoolean("readForAll");
1308
                                if (perms != null || readForAll != null) {
1309
                                        final Boolean fReadForAll = readForAll;
1310
                                        final Set<PermissionDTO> fPerms = perms;
1311
                                        new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1312
                                                @Override
1313
                                                public Object call() throws Exception {
1314
                                                        getService().setFilePermissions(user.getId(), file.getId(), fReadForAll, fPerms);
1315
                                                        return null;
1316
                                                }
1317

    
1318
                                        });
1319
                                }
1320

    
1321
                                if (json.opt("versioned") != null) {
1322
                                        final boolean versioned = json.getBoolean("versioned");
1323
                                        new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1324
                                                @Override
1325
                                                public Object call() throws Exception {
1326
                                                        getService().toggleFileVersioning(user.getId(), file.getId(), versioned);
1327
                                                        return null;
1328
                                                }
1329

    
1330
                                        });
1331
                                }
1332
                        }
1333
                } catch (JSONException e) {
1334
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1335
                } catch (InsufficientPermissionsException e) {
1336
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1337
                } catch (ObjectNotFoundException e) {
1338
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1339
                } catch (DuplicateNameException e) {
1340
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1341
                } catch (RpcException e) {
1342
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1343
                } catch (Exception e) {
1344
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1345
                        return;
1346
                }
1347
        }
1348

    
1349
        /**
1350
         * Helper method to convert a JSON array of permissions into a set of
1351
         * PermissionDTO objects.
1352
         *
1353
         * @param user the current user
1354
         * @param permissions the JSON array to parse
1355
         * @return the parsed set of permissions
1356
         * @throws JSONException if there was an error parsing the JSON object
1357
         * @throws RpcException if there was an error communicating with the EJB
1358
         * @throws ObjectNotFoundException if the user could not be found
1359
         */
1360
        private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1361
                        throws JSONException, RpcException, ObjectNotFoundException {
1362
                if (permissions == null)
1363
                        return null;
1364
                Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1365
                for (int i = 0; i < permissions.length(); i++) {
1366
                        JSONObject j = permissions.getJSONObject(i);
1367
                        PermissionDTO perm = new PermissionDTO();
1368
                        perm.setModifyACL(j.optBoolean("modifyACL"));
1369
                        perm.setRead(j.optBoolean("read"));
1370
                        perm.setWrite(j.optBoolean("write"));
1371
                        String permUser = j.optString("user");
1372
                        if (!permUser.isEmpty()) {
1373
                                User u = getService().findUser(permUser);
1374
                                if (u == null)
1375
                                        throw new ObjectNotFoundException("User " + permUser + " not found");
1376
                                perm.setUser(u.getDTO());
1377
                        }
1378
                        String permGroup = j.optString("group");
1379
                        if (!permGroup.isEmpty()) {
1380
                                GroupDTO g = getService().getGroup(user.getId(), permGroup);
1381
                                perm.setGroup(g);
1382
                        }
1383
                        if (permUser.isEmpty() && permGroup.isEmpty() ||
1384
                                                permUser.isEmpty() && permGroup.isEmpty())
1385
                                throw new JSONException("A permission must correspond to either a user or a group");
1386
                        perms.add(perm);
1387
                }
1388
                return perms;
1389
        }
1390

    
1391
        /**
1392
         * Creates a new folder with the specified name under the folder in the provided path.
1393
         *
1394
         * @param req the HTTP request
1395
         * @param resp the HTTP response
1396
         * @param path the parent folder path
1397
         * @param folderName the name of the new folder
1398
         * @throws IOException if an input/output error occurs
1399
         */
1400
        private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1401
                if (logger.isDebugEnabled())
1402
                           logger.debug("Creating folder " + folderName + " in '" + path);
1403

    
1404
            final User user = getUser(req);
1405
            User owner = getOwner(req);
1406
        boolean exists = true;
1407
        try {
1408
                getService().getResourceAtPath(owner.getId(), path + folderName, false);
1409
        } catch (ObjectNotFoundException e) {
1410
            exists = false;
1411
        } catch (RpcException e) {
1412
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1413
                        return;
1414
                }
1415

    
1416
        if (exists) {
1417
            resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1418
                                    ", " + METHOD_HEAD);
1419
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1420
            return;
1421
        }
1422

    
1423
                Object parent;
1424
                try {
1425
                        parent = getService().getResourceAtPath(owner.getId(), path, true);
1426
                } catch (ObjectNotFoundException e) {
1427
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1428
                        return;
1429
                } catch (RpcException e) {
1430
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1431
                        return;
1432
                }
1433
                try {
1434
                        if (parent instanceof FolderDTO) {
1435
                                final FolderDTO folder = (FolderDTO) parent;
1436
                                new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1437
                                        @Override
1438
                                        public Object call() throws Exception {
1439
                                                getService().createFolder(user.getId(), folder.getId(), folderName);
1440
                                                return null;
1441
                                        }
1442

    
1443
                                });
1444
                        String newResource = getContextPath(req, true) + folderName;
1445
                        resp.setHeader("Location", newResource);
1446
                        resp.setContentType("text/plain");
1447
                        PrintWriter out = resp.getWriter();
1448
                        out.println(newResource);
1449
                        } else {
1450
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1451
                            return;
1452
                        }
1453
                } catch (DuplicateNameException e) {
1454
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1455
                    return;
1456
                } catch (InsufficientPermissionsException e) {
1457
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1458
                    return;
1459
                } catch (ObjectNotFoundException e) {
1460
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1461
                        return;
1462
                } catch (RpcException e) {
1463
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1464
                        return;
1465
                } catch (Exception e) {
1466
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1467
                        return;
1468
                }
1469
            resp.setStatus(HttpServletResponse.SC_CREATED);
1470
        }
1471

    
1472
        /**
1473
         * @param req
1474
         * @param resp
1475
         * @throws IOException
1476
         * @throws FileNotFoundException
1477
         */
1478
        void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1479
        String path = getInnerPath(req, PATH_FILES);
1480
                try {
1481
                    path = URLDecoder.decode(path, "UTF-8");
1482
                } catch (IllegalArgumentException e) {
1483
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1484
                        return;
1485
                }
1486
            if (logger.isDebugEnabled())
1487
                           logger.debug("Updating resource: " + path);
1488

    
1489
            final User user = getUser(req);
1490
            User owner = getOwner(req);
1491
            boolean exists = true;
1492
        Object resource = null;
1493
        FileHeaderDTO file = null;
1494
        try {
1495
                resource = getService().getResourceAtPath(owner.getId(), path, false);
1496
        } catch (ObjectNotFoundException e) {
1497
            exists = false;
1498
        } catch (RpcException e) {
1499
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1500
                        return;
1501
                }
1502

    
1503
        if (exists)
1504
                        if (resource instanceof FileHeaderDTO)
1505
                            file = (FileHeaderDTO) resource;
1506
                        else {
1507
                        resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1508
                            return;
1509
                }
1510
        boolean result = true;
1511

    
1512
        // Temporary content file used to support partial PUT.
1513
        File contentFile = null;
1514

    
1515
        Range range = parseContentRange(req, resp);
1516

    
1517
        InputStream resourceInputStream = null;
1518

    
1519
        // Append data specified in ranges to existing content for this
1520
        // resource - create a temporary file on the local filesystem to
1521
        // perform this operation.
1522
        // Assume just one range is specified for now
1523
        if (range != null) {
1524
            try {
1525
                                contentFile = executePartialPut(req, range, path);
1526
                        } catch (RpcException e) {
1527
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1528
                                return;
1529
                        } catch (ObjectNotFoundException e) {
1530
                                resp.sendError(HttpServletResponse.SC_CONFLICT);
1531
                        return;
1532
                        } catch (InsufficientPermissionsException e) {
1533
                                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1534
                        return;
1535
                        }
1536
            resourceInputStream = new FileInputStream(contentFile);
1537
        } else
1538
                        resourceInputStream = req.getInputStream();
1539

    
1540
        try {
1541
                FolderDTO folder = null;
1542
                Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1543
                if (!(parent instanceof FolderDTO)) {
1544
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1545
                        return;
1546
                }
1547
                       folder = (FolderDTO) parent;
1548
                final String name = getLastElement(path);
1549
                final String mimeType = context.getMimeType(name);
1550
                File uploadedFile = null;
1551
                try {
1552
                                uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1553
                        } catch (IOException ex) {
1554
                                throw new GSSIOException(ex, false);
1555
                        }
1556
                FileHeaderDTO fileDTO = null;
1557
            if (exists)
1558
                    fileDTO = getService().updateFileContents(user.getId(), file.getId(), mimeType, uploadedFile.getCanonicalFile().length(), uploadedFile.getAbsolutePath());
1559
                        else {
1560
                                final File uploadedf = uploadedFile;
1561
                                final FolderDTO parentf = folder;
1562
                                fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1563
                                        @Override
1564
                                        public FileHeaderDTO call() throws Exception {
1565
                                                return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1566
                                        }
1567

    
1568
                                });
1569
                        }
1570
            getService().updateAccounting(owner, new Date(), fileDTO.getFileSize());
1571
                        getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1572
        } catch(ObjectNotFoundException e) {
1573
            result = false;
1574
        } catch (RpcException e) {
1575
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1576
                        return;
1577
        } catch (IOException e) {
1578
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1579
                        return;
1580
                } catch (GSSIOException e) {
1581
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1582
                        return;
1583
                } catch (DuplicateNameException e) {
1584
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1585
                    return;
1586
                } catch (InsufficientPermissionsException e) {
1587
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1588
                    return;
1589
                } catch (QuotaExceededException e) {
1590
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1591
                    return;
1592
                } catch (Exception e) {
1593
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1594
                        return;
1595
                }
1596

    
1597
        if (result) {
1598
            if (exists)
1599
                                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1600
                        else
1601
                                resp.setStatus(HttpServletResponse.SC_CREATED);
1602
        } else
1603
                        resp.sendError(HttpServletResponse.SC_CONFLICT);
1604
        }
1605

    
1606
    /**
1607
     * Delete a resource.
1608
     *
1609
     * @param req The servlet request we are processing
1610
     * @param resp The servlet response we are processing
1611
         * @throws IOException if the response cannot be sent
1612
     */
1613
    void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1614
        String path = getInnerPath(req, PATH_FILES);
1615
            if (logger.isDebugEnabled())
1616
                           logger.debug("Deleting resource '" + path);
1617
            path = URLDecoder.decode(path, "UTF-8");
1618
            User user = getUser(req);
1619
            User owner = getOwner(req);
1620
            boolean exists = true;
1621
            Object object = null;
1622
            try {
1623
                    object = getService().getResourceAtPath(owner.getId(), path, false);
1624
            } catch (ObjectNotFoundException e) {
1625
                    exists = false;
1626
            } catch (RpcException e) {
1627
                    resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1628
                        return;
1629
                }
1630

    
1631
            if (!exists) {
1632
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1633
                    return;
1634
            }
1635

    
1636
            FolderDTO folder = null;
1637
            FileHeaderDTO file = null;
1638
            if (object instanceof FolderDTO)
1639
                    folder = (FolderDTO) object;
1640
            else
1641
                    file = (FileHeaderDTO) object;
1642

    
1643
            if (file != null)
1644
                        try {
1645
                                getService().deleteFile(user.getId(), file.getId());
1646
                } catch (InsufficientPermissionsException e) {
1647
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1648
                                return;
1649
                    } catch (ObjectNotFoundException e) {
1650
                            // Although we had already found the object, it was
1651
                            // probably deleted from another thread.
1652
                            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1653
                            return;
1654
                    } catch (RpcException e) {
1655
                            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1656
                            return;
1657
                    }
1658
                else if (folder != null)
1659
                        try {
1660
                            getService().deleteFolder(user.getId(), folder.getId());
1661
                } catch (InsufficientPermissionsException e) {
1662
                        resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1663
                        return;
1664
                    } catch (ObjectNotFoundException e) {
1665
                        resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1666
                        return;
1667
                    } catch (RpcException e) {
1668
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1669
                        return;
1670
                    }
1671
                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1672
            return;
1673
    }
1674

    
1675
        /**
1676
     * Return an InputStream to a JSON representation of the contents
1677
     * of this directory.
1678
     *
1679
         * @param user the user that made the request
1680
     * @param folder the specified directory
1681
     * @return an input stream with the rendered contents
1682
         * @throws IOException if the response cannot be sent
1683
     * @throws ServletException
1684
         * @throws InsufficientPermissionsException if the user does not have
1685
         *                         the necessary privileges to read the directory
1686
     */
1687
    private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1688
                    ServletException, InsufficientPermissionsException {
1689
            JSONObject json = new JSONObject();
1690
            try {
1691
                        json.put("name", folder.getName()).
1692
                                        put("owner", folder.getOwner().getUsername()).
1693
                                        put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1694
                                        put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1695
                                        put("deleted", folder.isDeleted());
1696
                        if (folder.getAuditInfo().getModifiedBy() != null)
1697
                                json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1698
                                                put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1699
                        if (folder.getParent() != null) {
1700
                                JSONObject j = new JSONObject();
1701
                                j.put("uri", getApiRoot() + folder.getParent().getURI());
1702
                                j.put("name", folder.getParent().getName());
1703
                                json.put("parent", j);
1704
                        }
1705
                    List<JSONObject> subfolders = new ArrayList<JSONObject>();
1706
                    for (FolderDTO f: folder.getSubfolders())
1707
                                if (!f.isDeleted()) {
1708
                                        JSONObject j = new JSONObject();
1709
                                        j.put("name", f.getName()).
1710
                                                put("uri", getApiRoot() + f.getURI());
1711
                                        subfolders.add(j);
1712
                                }
1713
                    json.put("folders", subfolders);
1714
                    List<JSONObject> files = new ArrayList<JSONObject>();
1715
                    List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1716
                    for (FileHeaderDTO f: fileHeaders) {
1717
                            JSONObject j = new JSONObject();
1718
                                j.put("name", f.getName()).
1719
                                        put("owner", f.getOwner().getUsername()).
1720
                                        put("deleted", f.isDeleted()).
1721
                                        put("version", f.getVersion()).
1722
                                        put("content", f.getMimeType()).
1723
                                        put("size", f.getFileSize()).
1724
                                        put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1725
                                        put("path", f.getFolder().getPath()).
1726
                                        put("uri", getApiRoot() + f.getURI());
1727
                                if (f.getAuditInfo().getModificationDate() != null)
1728
                                        j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1729
                                files.add(j);
1730
                    }
1731
                    json.put("files", files);
1732
                    Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1733
                    json.put("permissions", renderJson(perms));
1734
                } catch (JSONException e) {
1735
                        throw new ServletException(e);
1736
                } catch (ObjectNotFoundException e) {
1737
                        throw new ServletException(e);
1738
                } catch (RpcException e) {
1739
                        throw new ServletException(e);
1740
                }
1741

    
1742
            // Prepare a writer to a buffered area
1743
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
1744
            OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1745
            PrintWriter writer = new PrintWriter(osWriter);
1746

    
1747
            // Return an input stream to the underlying bytes
1748
            writer.write(json.toString());
1749
            writer.flush();
1750
            return new ByteArrayInputStream(stream.toByteArray());
1751
    }
1752

    
1753
        /**
1754
     * Return a String with a JSON representation of the metadata
1755
     * of the specified folder.
1756
         * @throws RpcException
1757
         * @throws InsufficientPermissionsException
1758
         * @throws ObjectNotFoundException
1759
     */
1760
    private String renderJsonMetadata(User user, FolderDTO folder)
1761
                    throws ServletException, InsufficientPermissionsException {
1762
            // Check if the user has read permission.
1763
                try {
1764
                        if (!getService().canReadFolder(user.getId(), folder.getId()))
1765
                                throw new InsufficientPermissionsException();
1766
                } catch (ObjectNotFoundException e) {
1767
                        throw new ServletException(e);
1768
                } catch (RpcException e) {
1769
                        throw new ServletException(e);
1770
                }
1771

    
1772
            JSONObject json = new JSONObject();
1773
            try {
1774
                        json.put("name", folder.getName()).
1775
                        put("owner", folder.getOwner().getUsername()).
1776
                        put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1777
                        put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1778
                        put("deleted", folder.isDeleted());
1779
                        if (folder.getAuditInfo().getModifiedBy() != null)
1780
                                json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1781
                                                put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1782
                } catch (JSONException e) {
1783
                        throw new ServletException(e);
1784
                }
1785
            return json.toString();
1786
    }
1787

    
1788
        /**
1789
     * Return a String with a JSON representation of the metadata
1790
     * of the specified file. If an old file body is provided, then
1791
     * the metadata of that particular version will be returned.
1792
     *
1793
         * @param user the user that made the request
1794
     * @param file the specified file header
1795
     * @param oldBody the version number
1796
     * @return the JSON-encoded file
1797
     * @throws ServletException
1798
         * @throws InsufficientPermissionsException if the user does not have
1799
         *                         the necessary privileges to read the directory
1800
     */
1801
    private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
1802
                    throws ServletException, InsufficientPermissionsException {
1803
            JSONObject json = new JSONObject();
1804
            try {
1805
                    // Need to encode file name in order to properly display it in the web client.
1806
                        json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
1807
                                        put("owner", file.getOwner().getUsername()).
1808
                                        put("versioned", file.isVersioned()).
1809
                                        put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
1810
                                        put("readForAll", file.isReadForAll()).
1811
                                        put("tags", renderJson(file.getTags())).
1812
                                        put("path", file.getFolder().getPath()).
1813
                                    put("uri", getApiRoot() + file.getURI()).
1814
                                        put("deleted", file.isDeleted());
1815
                        JSONObject j = new JSONObject();
1816
                        j.put("uri", getApiRoot() + file.getFolder().getURI()).
1817
                                        put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
1818
                        json.put("folder", j);
1819
                        if (oldBody != null)
1820
                                json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
1821
                                                put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
1822
                                                put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
1823
                                                put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
1824
                                                put("content", oldBody.getMimeType()).
1825
                                                put("size", oldBody.getFileSize());
1826
                        else
1827
                                json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
1828
                                                put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
1829
                                                put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
1830
                                                put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
1831
                                                put("content", file.getMimeType()).
1832
                                                put("size", file.getFileSize());
1833
                    Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
1834
                    json.put("permissions", renderJson(perms));
1835
                } catch (JSONException e) {
1836
                        throw new ServletException(e);
1837
                } catch (ObjectNotFoundException e) {
1838
                        throw new ServletException(e);
1839
                } catch (RpcException e) {
1840
                        throw new ServletException(e);
1841
                } catch (UnsupportedEncodingException e) {
1842
                        throw new ServletException(e);
1843
                }
1844

    
1845
            return json.toString();
1846
    }
1847

    
1848
        /**
1849
         * Return a String with a JSON representation of the
1850
         * specified set of permissions.
1851
     *
1852
         * @param permissions the set of permissions
1853
         * @return the JSON-encoded object
1854
         * @throws JSONException
1855
         * @throws UnsupportedEncodingException
1856
         */
1857
        private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
1858
                JSONArray perms = new JSONArray();
1859
                for (PermissionDTO p: permissions) {
1860
                        JSONObject permission = new JSONObject();
1861
                        permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
1862
                        if (p.getUser() != null)
1863
                                permission.put("user", p.getUser().getUsername());
1864
                        if (p.getGroup() != null)
1865
                                permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
1866
                        perms.put(permission);
1867
                }
1868
                return perms;
1869
        }
1870

    
1871
        /**
1872
         * Return a String with a JSON representation of the
1873
         * specified collection of tags.
1874
     *
1875
         * @param tags the collection of tags
1876
         * @return the JSON-encoded object
1877
         * @throws JSONException
1878
         * @throws UnsupportedEncodingException
1879
         */
1880
        private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
1881
                JSONArray tagArray = new JSONArray();
1882
                for (String t: tags)
1883
                        tagArray.put(URLEncoder.encode(t,"UTF-8"));
1884
                return tagArray;
1885
        }
1886

    
1887
        /**
1888
         * Retrieves the user who owns the destination namespace, for a
1889
         * copy or move request.
1890
         *
1891
         * @param req the HTTP request
1892
         * @return the owner of the namespace
1893
         */
1894
        protected User getDestinationOwner(HttpServletRequest req) {
1895
                return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
1896
        }
1897

    
1898
        /**
1899
         * A helper inner class for updating the progress status of a file upload.
1900
         *
1901
         * @author kman
1902
         */
1903
        public static class StatusProgressListener implements ProgressListener {
1904
                private int percentLogged = 0;
1905
                private long bytesTransferred = 0;
1906

    
1907
                private long fileSize = -100;
1908

    
1909
                private long tenKBRead = -1;
1910

    
1911
                private Long userId;
1912

    
1913
                private String filename;
1914

    
1915
                private ExternalAPI service;
1916

    
1917
                public StatusProgressListener(ExternalAPI aService) {
1918
                        service = aService;
1919
                }
1920

    
1921
                /**
1922
                 * Modify the userId.
1923
                 *
1924
                 * @param aUserId the userId to set
1925
                 */
1926
                public void setUserId(Long aUserId) {
1927
                        userId = aUserId;
1928
                }
1929

    
1930
                /**
1931
                 * Modify the filename.
1932
                 *
1933
                 * @param aFilename the filename to set
1934
                 */
1935
                public void setFilename(String aFilename) {
1936
                        filename = aFilename;
1937
                }
1938

    
1939
                public void update(long bytesRead, long contentLength, int items) {
1940
                        //monitoring per percent of bytes uploaded
1941
                        bytesTransferred = bytesRead;
1942
                        if (fileSize != contentLength)
1943
                                fileSize = contentLength;
1944
                        int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
1945

    
1946
                        if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
1947
                                if (percent != percentLogged){
1948
                                        percentLogged = percent;
1949
                                        try {
1950
                                                if (userId != null && filename != null)
1951
                                                        service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
1952
                                        } catch (ObjectNotFoundException e) {
1953
                                                // Swallow the exception since it is going to be caught
1954
                                                // by previously called methods
1955
                                        }
1956
                                }
1957
                }
1958
        }
1959
}