Use exponential backoff when updating the password or last login time in WebDAV.
[pithos] / src / gr / ebs / gss / server / rest / FilesHandler.java
index 60a5ace..8e841c9 100644 (file)
@@ -32,6 +32,7 @@ import gr.ebs.gss.server.domain.dto.FolderDTO;
 import gr.ebs.gss.server.domain.dto.GroupDTO;
 import gr.ebs.gss.server.domain.dto.PermissionDTO;
 import gr.ebs.gss.server.ejb.ExternalAPI;
+import gr.ebs.gss.server.ejb.TransactionHelper;
 import gr.ebs.gss.server.webdav.Range;
 
 import java.io.BufferedReader;
@@ -57,6 +58,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.StringTokenizer;
+import java.util.concurrent.Callable;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
@@ -135,6 +137,23 @@ public class FilesHandler extends RequestHandler {
                context = servletContext;
        }
 
+       private void updateAccounting(final User user, final Date date, final long bandwidthDiff) {
+               try {
+                       new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                               @Override
+                               public Void call() throws Exception {
+                                       getService().updateAccounting(user, date, bandwidthDiff);
+                                       return null;
+                               }
+                       });
+               } catch (RuntimeException e) {
+                       throw e;
+               } catch (Exception e) {
+                       // updateAccounting() doesn't throw any checked exceptions
+                       assert false;
+               }
+       }
+
        /**
      * Serve the specified resource, optionally including the data content.
      *
@@ -320,10 +339,13 @@ public class FilesHandler extends RequestHandler {
        // satisfied. Doing this for folders would require recursive checking
        // for all of their children, which in turn would defy the purpose of
        // the optimization.
-       if (folder == null)
+       if (folder == null){
                        // Checking If headers.
                if (!checkIfHeaders(req, resp, file, oldBody))
                                return;
+       }
+       else if(!checkIfModifiedSince(req, resp, folder))
+               return;
 
        // Find content type.
        String contentType = null;
@@ -428,14 +450,14 @@ public class FilesHandler extends RequestHandler {
                        try {
                                if(file != null)
                                                if (needsContentDisposition(req))
-                                               resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+URLEncoder.encode(file.getName(),"UTF-8"));
+                                               resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
                                        else
-                                               resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+URLEncoder.encode(file.getName(),"UTF-8"));
+                                               resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
                                if (ostream != null)
                                                copy(file, renderResult, ostream, req, oldBody);
                                        else
                                                copy(file, renderResult, writer, req, oldBody);
-                               if (file!=null) getService().updateAccounting(user, new Date(), contentLength);
+                               if (file!=null) updateAccounting(owner, new Date(), contentLength);
                        } catch (ObjectNotFoundException e) {
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
                                return;
@@ -445,7 +467,7 @@ public class FilesHandler extends RequestHandler {
                        } catch (RpcException e) {
                                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                                return;
-                       }
+                       }
                }
        } else {
                if (ranges == null || ranges.isEmpty())
@@ -483,7 +505,7 @@ public class FilesHandler extends RequestHandler {
                                                        copy(file, ostream, range, req, oldBody);
                                                else
                                                        copy(file, writer, range, req, oldBody);
-                                       getService().updateAccounting(user, new Date(), contentLength);
+                                       updateAccounting(owner, new Date(), contentLength);
                        } catch (ObjectNotFoundException e) {
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
                                return;
@@ -508,7 +530,7 @@ public class FilesHandler extends RequestHandler {
                                                        copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
                                                else
                                                        copy(file, writer, ranges.iterator(), contentType, req, oldBody);
-                                       getService().updateAccounting(user, new Date(), contentLength);
+                                       updateAccounting(owner, new Date(), contentLength);
                        } catch (ObjectNotFoundException e) {
                                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
                                return;
@@ -525,6 +547,14 @@ public class FilesHandler extends RequestHandler {
     }
 
        /**
+        * Return the filename of the specified file properly formatted for
+        * including in the Content-Disposition header.
+        */
+       private String getDispositionFilename(FileHeaderDTO file) throws UnsupportedEncodingException {
+               return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
+       }
+
+       /**
         * Determines whether the user agent needs the Content-Disposition
         * header to be set, in order to properly download a file.
         *
@@ -632,7 +662,8 @@ public class FilesHandler extends RequestHandler {
                else if (restoreVersion != null)
                        restoreVersion(req, resp, path, restoreVersion);
                else
-                       resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+                       // IE with Gears uses POST for multiple uploads.
+                       putResource(req, resp);
        }
 
        /**
@@ -645,7 +676,7 @@ public class FilesHandler extends RequestHandler {
         * @throws IOException if an I/O error occurs
         */
        private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
-               User user = getUser(req);
+               final User user = getUser(req);
                User owner = getOwner(req);
                Object resource = null;
                try {
@@ -663,9 +694,16 @@ public class FilesHandler extends RequestHandler {
                }
 
                try {
-                       FileHeaderDTO file = (FileHeaderDTO) resource;
-                       int oldVersion = Integer.parseInt(version);
-                       getService().restoreVersion(user.getId(), file.getId(), oldVersion);
+                       final FileHeaderDTO file = (FileHeaderDTO) resource;
+                       final int oldVersion = Integer.parseInt(version);
+
+                       new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                               @Override
+                               public Void call() throws Exception {
+                                       getService().restoreVersion(user.getId(), file.getId(), oldVersion);
+                                       return null;
+                               }
+                       });
                } catch (InsufficientPermissionsException e) {
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                } catch (ObjectNotFoundException e) {
@@ -678,6 +716,8 @@ public class FilesHandler extends RequestHandler {
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
                } catch (NumberFormatException e) {
                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
+               } catch (Exception e) {
+                       resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
                }
        }
 
@@ -720,7 +760,6 @@ public class FilesHandler extends RequestHandler {
                        return;
                }
 
-        FolderDTO folder = null;
        Object parent;
        String parentPath = null;
                try {
@@ -737,8 +776,8 @@ public class FilesHandler extends RequestHandler {
                response.sendError(HttpServletResponse.SC_CONFLICT);
                return;
        }
-               folder = (FolderDTO) parent;
-       String fileName = getLastElement(path);
+       final FolderDTO folder = (FolderDTO) parent;
+       final String fileName = getLastElement(path);
 
                FileItemIterator iter;
                File uploadedFile = null;
@@ -820,7 +859,7 @@ public class FilesHandler extends RequestHandler {
 
                                        progressListener.setUserId(user.getId());
                                        progressListener.setFilename(fileName);
-                                       String contentType = item.getContentType();
+                                       final String contentType = item.getContentType();
 
                                        try {
                                                uploadedFile = getService().uploadFile(stream, user.getId());
@@ -828,11 +867,24 @@ public class FilesHandler extends RequestHandler {
                                                throw new GSSIOException(ex, false);
                                        }
                                        FileHeaderDTO fileDTO = null;
+                                       final File upf = uploadedFile;
+                                       final FileHeaderDTO f = file;
+                                       final User u = user;
                                        if (file == null)
-                                               fileDTO = getService().createFile(user.getId(), folder.getId(), fileName, contentType, uploadedFile);
+                                               fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
+                                                       @Override
+                                                       public FileHeaderDTO call() throws Exception {
+                                                               return getService().createFile(u.getId(), folder.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
+                                                       }
+                                               });
                                        else
-                                               fileDTO = getService().updateFileContents(user.getId(), file.getId(), contentType, uploadedFile);
-                                       getService().updateAccounting(user, new Date(), fileDTO.getFileSize());
+                                               fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
+                                                       @Override
+                                                       public FileHeaderDTO call() throws Exception {
+                                                               return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
+                                                       }
+                                               });
+                                       updateAccounting(owner, new Date(), fileDTO.getFileSize());
                                        getService().removeFileUploadProgress(user.getId(), fileName);
                                }
                        }
@@ -886,44 +938,15 @@ public class FilesHandler extends RequestHandler {
                        String error = "An error occurred while communicating with the service";
                        logger.error(error, e);
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
+               } catch (Exception e) {
+                       if (uploadedFile != null && uploadedFile.exists())
+                               uploadedFile.delete();
+                       String error = "An internal server error occurred";
+                       logger.error(error, e);
+                       response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
                }
        }
 
-       private String getBackupFilename(FileHeaderDTO file, String filename){
-               List<FileHeaderDTO> deletedFiles = new ArrayList<FileHeaderDTO>();
-               try{
-                       deletedFiles = getService().getDeletedFiles(file.getOwner().getId());
-               }
-               catch(ObjectNotFoundException e){
-
-               } catch (RpcException e) {
-
-               }
-               List<FileHeaderDTO> filesInSameFolder = new ArrayList<FileHeaderDTO>();
-               for(FileHeaderDTO deleted : deletedFiles)
-                       if(deleted.getFolder().getId().equals(file.getFolder().getId()))
-                               filesInSameFolder.add(deleted);
-               int i=1;
-               String filenameToCheck = filename;
-               for(FileHeaderDTO same : filesInSameFolder)
-                       if(same.getName().startsWith(filename)){
-                               String toCheck=same.getName().substring(filename.length(),same.getName().length());
-                               if(toCheck.startsWith(" ")){
-                                       int test =-1;
-                                       try{
-                                               test = Integer.valueOf(toCheck.replace(" ",""));
-                                       }
-                                       catch(NumberFormatException e){
-                                               //do nothing since string is not a number
-                                       }
-                                       if(test>=i)
-                                               i = test+1;
-                               }
-                       }
-
-               return filename+" "+i;
-       }
-
        /**
         * Move the resource in the specified path to the specified destination.
         *
@@ -934,7 +957,7 @@ public class FilesHandler extends RequestHandler {
         * @throws IOException if an input/output error occurs
         */
        private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
-               User user = getUser(req);
+               final User user = getUser(req);
                User owner = getOwner(req);
                Object resource = null;
                try {
@@ -970,12 +993,27 @@ public class FilesHandler extends RequestHandler {
                }
 
                try {
+                       final User dOwner = destOwner;
+                       final String dest = destination;
                        if (resource instanceof FolderDTO) {
-                               FolderDTO folder = (FolderDTO) resource;
-                               getService().moveFolderToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
+                               final FolderDTO folder = (FolderDTO) resource;
+                               new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                                       @Override
+                                       public Void call() throws Exception {
+                                               getService().moveFolderToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
+                                               return null;
+                                       }
+                               });
                        } else {
-                               FileHeaderDTO file = (FileHeaderDTO) resource;
-                               getService().moveFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
+                               final FileHeaderDTO file = (FileHeaderDTO) resource;
+                               new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                                       @Override
+                                       public Void call() throws Exception {
+                                               getService().moveFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
+                                               return null;
+                                       }
+                               });
+
                        }
                } catch (InsufficientPermissionsException e) {
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
@@ -989,6 +1027,8 @@ public class FilesHandler extends RequestHandler {
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
                } catch (QuotaExceededException e) {
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
+               } catch (Exception e) {
+                       resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
                }
        }
 
@@ -1002,7 +1042,7 @@ public class FilesHandler extends RequestHandler {
         * @throws IOException if an input/output error occurs
         */
        private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
-               User user = getUser(req);
+               final User user = getUser(req);
                User owner = getOwner(req);
                Object resource = null;
                try {
@@ -1019,10 +1059,10 @@ public class FilesHandler extends RequestHandler {
         User destOwner = null;
                boolean exists = true;
                try {
-                       destination = getDestinationPath(req, encodePath(copyTo));
-                       destination = URLDecoder.decode(destination, "UTF-8");
+                       String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
+                       destination = URLDecoder.decode(destinationEncoded, "UTF-8");
                        destOwner = getDestinationOwner(req);
-                       getService().getResourceAtPath(destOwner.getId(), destination, true);
+                       getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
                } catch (ObjectNotFoundException e) {
                        exists = false;
                } catch (URISyntaxException e) {
@@ -1038,12 +1078,26 @@ public class FilesHandler extends RequestHandler {
                }
 
                try {
+                       final User dOwner = destOwner;
+                       final String dest = destination;
                        if (resource instanceof FolderDTO) {
-                               FolderDTO folder = (FolderDTO) resource;
-                               getService().copyFolderStructureToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
+                               final FolderDTO folder = (FolderDTO) resource;
+                               new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                                       @Override
+                                       public Void call() throws Exception {
+                                               getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
+                                               return null;
+                                       }
+                               });
                        } else {
-                               FileHeaderDTO file = (FileHeaderDTO) resource;
-                               getService().copyFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
+                               final FileHeaderDTO file = (FileHeaderDTO) resource;
+                               new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                                       @Override
+                                       public Void call() throws Exception {
+                                               getService().copyFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
+                                               return null;
+                                       }
+                               });
                        }
                } catch (InsufficientPermissionsException e) {
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
@@ -1057,6 +1111,8 @@ public class FilesHandler extends RequestHandler {
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
                } catch (QuotaExceededException e) {
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
+               } catch (Exception e) {
+                       resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
                }
        }
 
@@ -1074,6 +1130,7 @@ public class FilesHandler extends RequestHandler {
        /**
         * A helper method that extracts the relative resource path,
         * after removing the 'files' namespace.
+        * The path returned is <i>not</i> URL-decoded.
         *
         * @param req the HTTP request
         * @param path the specified path
@@ -1081,10 +1138,11 @@ public class FilesHandler extends RequestHandler {
         * @throws URISyntaxException
         * @throws RpcException in case an error occurs while communicating
         *                                              with the backend
+        * @throws UnsupportedEncodingException
         */
-       private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException {
+       private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
                URI uri = new URI(path);
-               String dest = uri.getPath();
+               String dest = uri.getRawPath();
                // Remove the context path from the destination URI.
                String contextPath = req.getContextPath();
                if (!dest.startsWith(contextPath))
@@ -1101,7 +1159,8 @@ public class FilesHandler extends RequestHandler {
                int slash = dest.substring(1).indexOf('/');
                if (slash == -1)
                        throw new URISyntaxException(dest, "No username in the destination URI");
-               String owner = dest.substring(1, slash + 1);
+               // Decode the user to get the proper characters (mainly the @)
+               String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
                User o;
                o = getService().findUser(owner);
                if (o == null)
@@ -1126,7 +1185,7 @@ public class FilesHandler extends RequestHandler {
         * @throws IOException if an input/output error occurs
         */
        private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
-               User user = getUser(req);
+               final User user = getUser(req);
                User owner = getOwner(req);
                Object resource = null;
                try {
@@ -1141,11 +1200,23 @@ public class FilesHandler extends RequestHandler {
 
                try {
                        if (resource instanceof FolderDTO) {
-                               FolderDTO folder = (FolderDTO) resource;
-                               getService().moveFolderToTrash(user.getId(), folder.getId());
+                               final FolderDTO folder = (FolderDTO) resource;
+                               new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                                       @Override
+                                       public Void call() throws Exception {
+                                               getService().moveFolderToTrash(user.getId(), folder.getId());
+                                               return null;
+                                       }
+                               });
                        } else {
-                               FileHeaderDTO file = (FileHeaderDTO) resource;
-                               getService().moveFileToTrash(user.getId(), file.getId());
+                               final FileHeaderDTO file = (FileHeaderDTO) resource;
+                               new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                                       @Override
+                                       public Void call() throws Exception {
+                                               getService().moveFileToTrash(user.getId(), file.getId());
+                                               return null;
+                                       }
+                               });
                        }
                } catch (InsufficientPermissionsException e) {
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
@@ -1153,6 +1224,8 @@ public class FilesHandler extends RequestHandler {
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
                } catch (RpcException e) {
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
+               } catch (Exception e) {
+                       resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
                }
        }
 
@@ -1165,7 +1238,7 @@ public class FilesHandler extends RequestHandler {
         * @throws IOException if an input/output error occurs
         */
        private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
-               User user = getUser(req);
+               final User user = getUser(req);
                User owner = getOwner(req);
                Object resource = null;
                try {
@@ -1180,11 +1253,23 @@ public class FilesHandler extends RequestHandler {
 
                try {
                        if (resource instanceof FolderDTO) {
-                               FolderDTO folder = (FolderDTO) resource;
-                               getService().removeFolderFromTrash(user.getId(), folder.getId());
+                               final FolderDTO folder = (FolderDTO) resource;
+                               new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                                       @Override
+                                       public Void call() throws Exception {
+                                               getService().removeFolderFromTrash(user.getId(), folder.getId());
+                                               return null;
+                                       }
+                               });
                        } else {
-                               FileHeaderDTO file = (FileHeaderDTO) resource;
-                               getService().removeFileFromTrash(user.getId(), file.getId());
+                               final FileHeaderDTO file = (FileHeaderDTO) resource;
+                               new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                                       @Override
+                                       public Void call() throws Exception {
+                                               getService().removeFileFromTrash(user.getId(), file.getId());
+                                               return null;
+                                       }
+                               });
                        }
                } catch (InsufficientPermissionsException e) {
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
@@ -1192,6 +1277,8 @@ public class FilesHandler extends RequestHandler {
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
                } catch (RpcException e) {
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
+               } catch (Exception e) {
+                       resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
                }
        }
 
@@ -1204,7 +1291,7 @@ public class FilesHandler extends RequestHandler {
         * @throws IOException if an input/output error occurs
         */
        private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
-               User user = getUser(req);
+               final User user = getUser(req);
                User owner = getOwner(req);
                Object resource = null;
                try {
@@ -1229,36 +1316,42 @@ public class FilesHandler extends RequestHandler {
                        if (logger.isDebugEnabled())
                                logger.debug("JSON update: " + json);
                        if (resource instanceof FolderDTO) {
-                               FolderDTO folder = (FolderDTO) resource;
+                               final FolderDTO folder = (FolderDTO) resource;
                                String name = json.optString("name");
-                               if (!name.isEmpty()){
+                               if (!name.isEmpty())
                                        try {
                                                name = URLDecoder.decode(name, "UTF-8");
                                        } catch (IllegalArgumentException e) {
                                                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
                                                return;
                                        }
-                                       getService().modifyFolder(user.getId(), folder.getId(), name);
-                                       FolderDTO folderUpdated = getService().getFolder(user.getId(), folder.getId());
-                                       String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
-                                       String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
-                                       parentUrl = parentUrl.replaceAll(fpath, "");
-                                       if(!parentUrl.endsWith("/"))
-                                               parentUrl = parentUrl+"/";
-                                       parentUrl = parentUrl+folderUpdated.getOwner().getUsername()+PATH_FILES+folderUpdated.getPath();
-                                       resp.getWriter().println(parentUrl);
-                               }
-
                                JSONArray permissions = json.optJSONArray("permissions");
-                               if (permissions != null) {
-                                       Set<PermissionDTO> perms = parsePermissions(user, permissions);
-                                       getService().setFolderPermissions(user.getId(), folder.getId(), perms);
+                               Set<PermissionDTO> perms = null;
+                               if (permissions != null)
+                                       perms = parsePermissions(user, permissions);
+                               if (!name.isEmpty() || permissions != null) {
+                                       final String fName = name.isEmpty()? null: name;
+                                       final Set<PermissionDTO> fPerms = perms;
+                                       FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
+                                               @Override
+                                               public FolderDTO call() throws Exception {
+                                                       return getService().updateFolder(user.getId(), folder.getId(), fName, fPerms);
+                                               }
+
+                                       });
+                                       resp.getWriter().println(getNewUrl(req, folderUpdated));
                                }
                        } else {
-                               FileHeaderDTO file = (FileHeaderDTO) resource;
+                               final FileHeaderDTO file = (FileHeaderDTO) resource;
                                String name = null;
                                if (json.opt("name") != null)
                                        name = json.optString("name");
+                               Long modificationDate = null;
+                               if (json.optLong("modificationDate") != 0)
+                                       modificationDate = json.optLong("modificationDate");
+                               Boolean versioned = null;
+                               if (json.opt("versioned") != null)
+                                       versioned = json.getBoolean("versioned");
                                JSONArray tagset = json.optJSONArray("tags");
                                String tags = null;
                                StringBuffer t = new StringBuffer();
@@ -1267,9 +1360,6 @@ public class FilesHandler extends RequestHandler {
                                                t.append(tagset.getString(i) + ',');
                                        tags = t.toString();
                                }
-                               if (name != null || tags != null)
-                                       getService().updateFile(user.getId(), file.getId(), name, tags);
-
                                JSONArray permissions = json.optJSONArray("permissions");
                                Set<PermissionDTO> perms = null;
                                if (permissions != null)
@@ -1277,12 +1367,25 @@ public class FilesHandler extends RequestHandler {
                                Boolean readForAll = null;
                                if (json.opt("readForAll") != null)
                                        readForAll = json.optBoolean("readForAll");
-                               if (perms != null || readForAll != null)
-                                       getService().setFilePermissions(user.getId(), file.getId(), readForAll, perms);
-
-                               if (json.opt("versioned") != null) {
-                                       boolean versioned = json.getBoolean("versioned");
-                                       getService().toggleFileVersioning(user.getId(), file.getId(), versioned);
+                               if (name != null || tags != null || modificationDate != null
+                                                       || versioned != null || perms != null
+                                                       || readForAll != null) {
+                                       final String fName = name;
+                                       final String fTags = tags;
+                                       final Date mDate = modificationDate != null? new Date(modificationDate): null;
+                                       final Boolean fVersioned = versioned;
+                                       final Boolean fReadForAll = readForAll;
+                                       final Set<PermissionDTO> fPerms = perms;
+                                       new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
+                                               @Override
+                                               public Object call() throws Exception {
+                                                       getService().updateFile(user.getId(), file.getId(),
+                                                                               fName, fTags, mDate, fVersioned,
+                                                                               fReadForAll, fPerms);
+                                                       return null;
+                                               }
+
+                                       });
                                }
                        }
                } catch (JSONException e) {
@@ -1295,10 +1398,27 @@ public class FilesHandler extends RequestHandler {
                        resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
                } catch (RpcException e) {
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
+               } catch (Exception e) {
+                       resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
+                       return;
                }
        }
 
        /**
+        * Returns the new URL of an updated folder.
+        */
+       private String getNewUrl(HttpServletRequest req, FolderDTO folder) throws UnsupportedEncodingException {
+               String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
+               String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
+               if (parentUrl.indexOf(fpath) != -1)
+                       parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
+               if(!parentUrl.endsWith("/"))
+                       parentUrl = parentUrl+"/";
+               parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
+               return parentUrl;
+       }
+
+       /**
         * Helper method to convert a JSON array of permissions into a set of
         * PermissionDTO objects.
         *
@@ -1308,9 +1428,10 @@ public class FilesHandler extends RequestHandler {
         * @throws JSONException if there was an error parsing the JSON object
         * @throws RpcException if there was an error communicating with the EJB
         * @throws ObjectNotFoundException if the user could not be found
+        * @throws UnsupportedEncodingException
         */
        private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
-                       throws JSONException, RpcException, ObjectNotFoundException {
+                       throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
                if (permissions == null)
                        return null;
                Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
@@ -1327,13 +1448,24 @@ public class FilesHandler extends RequestHandler {
                                        throw new ObjectNotFoundException("User " + permUser + " not found");
                                perm.setUser(u.getDTO());
                        }
+                       // 31/8/2009: Add optional groupUri which takes priority if it exists
+                       String permGroupUri = j.optString("groupUri");
                        String permGroup = j.optString("group");
-                       if (!permGroup.isEmpty()) {
+                       if (!permGroupUri.isEmpty()) {
+                               String[] names = permGroupUri.split("/");
+                               String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
+                               String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
+                               User u = getService().findUser(usr);
+                               if (u == null)
+                                       throw new ObjectNotFoundException("User " + permUser + " not found");
+                               GroupDTO g = getService().getGroup(u.getId(), grp);
+                               perm.setGroup(g);
+                       }
+                       else if (!permGroup.isEmpty()) {
                                GroupDTO g = getService().getGroup(user.getId(), permGroup);
                                perm.setGroup(g);
                        }
-                       if (permUser.isEmpty() && permGroup.isEmpty() ||
-                                               permUser.isEmpty() && permGroup.isEmpty())
+                       if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
                                throw new JSONException("A permission must correspond to either a user or a group");
                        perms.add(perm);
                }
@@ -1349,11 +1481,11 @@ public class FilesHandler extends RequestHandler {
         * @param folderName the name of the new folder
         * @throws IOException if an input/output error occurs
         */
-       private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, String folderName) throws IOException {
+       private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
                if (logger.isDebugEnabled())
                        logger.debug("Creating folder " + folderName + " in '" + path);
 
-       User user = getUser(req);
+       final User user = getUser(req);
        User owner = getOwner(req);
         boolean exists = true;
         try {
@@ -1384,9 +1516,15 @@ public class FilesHandler extends RequestHandler {
                }
                try {
                        if (parent instanceof FolderDTO) {
-                               FolderDTO folder = (FolderDTO) parent;
-                               getService().createFolder(user.getId(), folder.getId(), folderName);
-                       String newResource = getContextPath(req, true) + folderName;
+                               final FolderDTO folder = (FolderDTO) parent;
+                               FolderDTO newFolder = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
+                                       @Override
+                                       public FolderDTO call() throws Exception {
+                                               return getService().createFolder(user.getId(), folder.getId(), folderName);
+                                       }
+
+                               });
+                       String newResource = getApiRoot() + newFolder.getURI();
                        resp.setHeader("Location", newResource);
                        resp.setContentType("text/plain");
                    PrintWriter out = resp.getWriter();
@@ -1407,6 +1545,9 @@ public class FilesHandler extends RequestHandler {
                } catch (RpcException e) {
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
                        return;
+               } catch (Exception e) {
+               resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
+                       return;
                }
        resp.setStatus(HttpServletResponse.SC_CREATED);
        }
@@ -1428,7 +1569,7 @@ public class FilesHandler extends RequestHandler {
        if (logger.isDebugEnabled())
                        logger.debug("Updating resource: " + path);
 
-       User user = getUser(req);
+       final User user = getUser(req);
        User owner = getOwner(req);
        boolean exists = true;
         Object resource = null;
@@ -1487,8 +1628,8 @@ public class FilesHandler extends RequestHandler {
                        return;
                }
                        folder = (FolderDTO) parent;
-               String name = getLastElement(path);
-               String mimeType = context.getMimeType(name);
+               final String name = getLastElement(path);
+               final String mimeType = context.getMimeType(name);
                File uploadedFile = null;
                try {
                                uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
@@ -1496,11 +1637,25 @@ public class FilesHandler extends RequestHandler {
                                throw new GSSIOException(ex, false);
                        }
                FileHeaderDTO fileDTO = null;
+               final File uploadedf = uploadedFile;
+                       final FolderDTO parentf = folder;
+                       final FileHeaderDTO f = file;
             if (exists)
-               fileDTO = getService().updateFileContents(user.getId(), file.getId(), mimeType, uploadedFile);
+               fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
+                                       @Override
+                                       public FileHeaderDTO call() throws Exception {
+                                               return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
+                                       }
+                               });
                        else
-                               fileDTO = getService().createFile(user.getId(), folder.getId(), name, mimeType, uploadedFile);
-            getService().updateAccounting(user, new Date(), fileDTO.getFileSize());
+                               fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
+                                       @Override
+                                       public FileHeaderDTO call() throws Exception {
+                                               return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
+                                       }
+
+                               });
+            updateAccounting(owner, new Date(), fileDTO.getFileSize());
                        getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
         } catch(ObjectNotFoundException e) {
             result = false;
@@ -1522,6 +1677,9 @@ public class FilesHandler extends RequestHandler {
                } catch (QuotaExceededException e) {
                        resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
                return;
+               } catch (Exception e) {
+               resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
+                       return;
                }
 
         if (result) {
@@ -1545,7 +1703,7 @@ public class FilesHandler extends RequestHandler {
        if (logger.isDebugEnabled())
                        logger.debug("Deleting resource '" + path);
        path = URLDecoder.decode(path, "UTF-8");
-       User user = getUser(req);
+       final User user = getUser(req);
        User owner = getOwner(req);
        boolean exists = true;
        Object object = null;
@@ -1572,7 +1730,14 @@ public class FilesHandler extends RequestHandler {
 
        if (file != null)
                        try {
-                               getService().deleteFile(user.getId(), file.getId());
+                               final FileHeaderDTO f = file;
+                               new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                                       @Override
+                                       public Void call() throws Exception {
+                                               getService().deleteFile(user.getId(), f.getId());
+                                               return null;
+                                       }
+                               });
                } catch (InsufficientPermissionsException e) {
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                                return;
@@ -1584,10 +1749,20 @@ public class FilesHandler extends RequestHandler {
                } catch (RpcException e) {
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                        return;
+               } catch (Exception e) {
+                       resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                       return;
                }
                else if (folder != null)
                        try {
-                       getService().deleteFolder(user.getId(), folder.getId());
+                               final FolderDTO fo = folder;
+                               new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
+                                       @Override
+                                       public Void call() throws Exception {
+                                               getService().deleteFolder(user.getId(), fo.getId());
+                                               return null;
+                                       }
+                               });
                } catch (InsufficientPermissionsException e) {
                        resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                        return;
@@ -1597,6 +1772,9 @@ public class FilesHandler extends RequestHandler {
                } catch (RpcException e) {
                        resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                        return;
+               } catch (Exception e) {
+                       resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                       return;
                }
                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
        return;
@@ -1791,8 +1969,11 @@ public class FilesHandler extends RequestHandler {
                        permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
                        if (p.getUser() != null)
                                permission.put("user", p.getUser().getUsername());
-                       if (p.getGroup() != null)
+                       if (p.getGroup() != null) {
+                               GroupDTO group = p.getGroup();
+                               permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
                                permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
+                       }
                        perms.put(permission);
                }
                return perms;
@@ -1836,8 +2017,6 @@ public class FilesHandler extends RequestHandler {
 
                private long fileSize = -100;
 
-               private long tenKBRead = -1;
-
                private Long userId;
 
                private String filename;