Set all required objectClass attributes when adding an entry to LDAP. Also consistent...
[pithos] / src / gr / ebs / gss / server / ejb / ExternalAPIBean.java
index 94d9c91..0cb2971 100644 (file)
@@ -31,6 +31,7 @@ import gr.ebs.gss.server.domain.FileTag;
 import gr.ebs.gss.server.domain.FileUploadStatus;
 import gr.ebs.gss.server.domain.Folder;
 import gr.ebs.gss.server.domain.Group;
+import gr.ebs.gss.server.domain.Invitation;
 import gr.ebs.gss.server.domain.Nonce;
 import gr.ebs.gss.server.domain.Permission;
 import gr.ebs.gss.server.domain.User;
@@ -56,12 +57,14 @@ import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Random;
 import java.util.Set;
 import java.util.StringTokenizer;
 
 import javax.ejb.EJB;
 import javax.ejb.EJBException;
+import javax.ejb.EJBTransactionRolledbackException;
 import javax.ejb.Stateless;
 import javax.ejb.TransactionAttribute;
 import javax.ejb.TransactionAttributeType;
@@ -73,10 +76,10 @@ import javax.jms.MessageProducer;
 import javax.jms.Queue;
 import javax.jms.QueueConnectionFactory;
 import javax.jms.Session;
-import javax.jws.WebMethod;
 import javax.naming.Context;
 import javax.naming.InitialContext;
 import javax.naming.NamingException;
+import javax.persistence.PersistenceException;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
@@ -97,12 +100,19 @@ import org.apache.commons.httpclient.methods.StringRequestEntity;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.hibernate.exception.ConstraintViolationException;
 import org.w3c.dom.DOMException;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.SAXException;
 
+import com.novell.ldap.LDAPAttribute;
+import com.novell.ldap.LDAPAttributeSet;
+import com.novell.ldap.LDAPConnection;
+import com.novell.ldap.LDAPEntry;
+import com.novell.ldap.LDAPException;
+
 /**
  * The concrete implementation of the ExternalAPI interface.
  *
@@ -138,6 +148,20 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
         */
        private static Random random = new Random();
 
+       /**
+        * Mark the folder and all of its parent folders as modified from the specified user.
+        */
+       private void touchParentFolders(Folder folder, User user, Date date) {
+               Folder f = folder;
+               while (f != null) {
+                       AuditInfo ai = f.getAuditInfo();
+                       ai.setModifiedBy(user);
+                       ai.setModificationDate(date);
+                       f.setAuditInfo(ai);
+                       f = f.getParent();
+               }
+       }
+
        @Override
        public FolderDTO getRootFolder(Long userId) throws ObjectNotFoundException {
                if (userId == null)
@@ -146,11 +170,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                return folder.getDTO();
        }
 
-       /*
-        * (non-Javadoc)
-        *
-        * @see gr.ebs.gss.server.ejb.ExternalAPI#getFolder(java.lang.Long)
-        */
+       @Override
        public FolderDTO getFolder(final Long userId, final Long folderId) throws ObjectNotFoundException, InsufficientPermissionsException {
                if (userId == null)
                        throw new ObjectNotFoundException("No user specified");
@@ -263,7 +283,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
        }
 
        @Override
-       public void createFolder(Long userId, Long parentId, String name)
+       public FolderDTO createFolder(Long userId, Long parentId, String name)
                        throws DuplicateNameException, ObjectNotFoundException, InsufficientPermissionsException {
                // Validate.
                if (userId == null)
@@ -290,7 +310,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                                        " to write to this folder");
 
                // Do the actual work.
-               createFolder(name, parent, creator);
+               return createFolder(name, parent, creator);
        }
 
        /**
@@ -299,8 +319,9 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
         * @param name
         * @param parent
         * @param creator
+        * @return the new folder
         */
-       private void createFolder(String name, Folder parent, User creator) {
+       private FolderDTO createFolder(String name, Folder parent, User creator) {
                Folder folder = new Folder();
                folder.setName(name);
                if (parent != null) {
@@ -316,6 +337,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                auditInfo.setModifiedBy(creator);
                auditInfo.setModificationDate(now);
                folder.setAuditInfo(auditInfo);
+               touchParentFolders(folder, auditInfo.getModifiedBy(), auditInfo.getModificationDate());
 
                if (parent != null)
                        for (Permission p : parent.getPermissions()) {
@@ -336,10 +358,12 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                        folder.addPermission(permission);
                }
                dao.create(folder);
+               return folder.getDTO();
        }
 
        /*
-        * (non-Javadoc)
+        * Deletes the given folder and all its subfolders and files
+        * Only the permissions for top folder are checked
         *
         * @see gr.ebs.gss.server.ejb.ExternalAPI#deleteFolder(java.lang.Long,
         *      java.lang.Long)
@@ -361,8 +385,28 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                        logger.info("User " + user.getId() + " cannot delete folder " + folder.getName() + "(" + folder.getId() + ")");
                        throw new InsufficientPermissionsException("User " + user.getId() + " cannot delete folder " + folder.getName() + "(" + folder.getId() + ")");
                }
+               removeSubfolderFiles(folder);
                parent.removeSubfolder(folder);
                dao.delete(folder);
+               touchParentFolders(parent, user, new Date());
+       }
+
+       /**
+        * Traverses the folder and deletes all actual files (file system)
+        * regardless of permissions
+        *
+        * @param folder
+        */
+       private void removeSubfolderFiles(Folder folder) {
+               //remove files for all subfolders
+               for (Folder subfolder:folder.getSubfolders())
+                       removeSubfolderFiles(subfolder);
+               //remove this folder's file bodies (actual files)
+               for (FileHeader file:folder.getFiles()) {
+                       for (FileBody body:file.getBodies())
+                               deleteActualFile(body.getStoredFilePath());
+                       indexFile(file.getId(), true);
+               }
        }
 
        @SuppressWarnings("unchecked")
@@ -385,30 +429,41 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
        }
 
        @Override
-       public void modifyFolder(Long userId, Long folderId, String folderName)
-                       throws InsufficientPermissionsException, ObjectNotFoundException, DuplicateNameException {
+       public FolderDTO updateFolder(Long userId, Long folderId, String folderName,
+                               Set<PermissionDTO> permissions)
+                       throws InsufficientPermissionsException, ObjectNotFoundException,
+                       DuplicateNameException {
 
                // Validate.
                if (userId == null)
                        throw new ObjectNotFoundException("No user specified");
                if (folderId == null)
                        throw new ObjectNotFoundException("No folder specified");
-               if (StringUtils.isEmpty(folderName))
-                       throw new ObjectNotFoundException("New folder name is empty");
 
                Folder folder = dao.getEntityById(Folder.class, folderId);
                User user = dao.getEntityById(User.class, userId);
-               if (!folder.hasWritePermission(user))
+               if (folderName != null && !folder.hasWritePermission(user))
+                       throw new InsufficientPermissionsException("You don't have the necessary permissions");
+               if(permissions != null && !permissions.isEmpty() && !folder.hasModifyACLPermission(user))
                        throw new InsufficientPermissionsException("You don't have the necessary permissions");
 
                Folder parent = folder.getParent();
-               if (parent != null)
-                       if (!folder.getName().equals(folderName) && dao.existsFolderOrFile(parent.getId(), folderName))
-                               throw new DuplicateNameException("A folder or file with the name '" + folderName + "' already exists at this level");
+               if (folderName != null) {
+                       if (parent != null)
+                               if (!folder.getName().equals(folderName) && dao.existsFolderOrFile(parent.getId(), folderName))
+                                       throw new DuplicateNameException("A folder or file with the name '" + folderName + "' already exists at this level");
+
+                       // Do the actual modification.
+                       folder.setName(folderName);
+               }
+               if (permissions != null)
+                       setFolderPermissions(user, folder, permissions);
 
-               // Do the actual modification.
-               folder.setName(folderName);
+               folder.getAuditInfo().setModificationDate(new Date());
+               folder.getAuditInfo().setModifiedBy(user);
                dao.update(folder);
+               touchParentFolders(folder, user, new Date());
+               return folder.getDTO();
        }
 
        /*
@@ -423,6 +478,8 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                        throw new ObjectNotFoundException("No user specified");
                if (StringUtils.isEmpty(name))
                        throw new ObjectNotFoundException("New group name is empty");
+               if (name.indexOf('/')>=0)
+                       throw new IllegalArgumentException("Character '/' is not allowed in group name");
                if (dao.existsGroup(userId, name))
                        throw new DuplicateNameException("A group with the name '" + name + "' already exists");
 
@@ -478,7 +535,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                        // Supply a more accurate problem description.
                        throw new GSSIOException("Problem creating file",ioe);
                }
-               return createFile(userId, folderId, name, mimeType, file);
+               return createFile(userId, folderId, name, mimeType, file.length(), file.getAbsolutePath());
        }
 
        /* (non-Javadoc)
@@ -570,21 +627,21 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                final User user = dao.getEntityById(User.class, userId);
                if (!file.hasDeletePermission(user))
                        throw new InsufficientPermissionsException("User " + user.getId() + " cannot delete file " + file.getName() + "(" + file.getId() + ")");
-               for (final FileBody body : file.getBodies()) {
-                       final File fileContents = new File(body.getStoredFilePath());
-                       if (!fileContents.delete())
-                               logger.error("Could not delete file " + body.getStoredFilePath());
-               }
+               for (final FileBody body : file.getBodies())
+                       deleteActualFile(body.getStoredFilePath());
                dao.delete(file);
+               touchParentFolders(parent, user, new Date());
                indexFile(fileId, true);
        }
 
-       /*
-        * (non-Javadoc)
-        *
-        * @see gr.ebs.gss.server.ejb.ExternalAPI#createTag(java.lang.Long,
-        *      java.lang.Long, java.lang.String)
-        */
+       private void deleteActualFile(String filePath) {
+               if (filePath == null)
+                       return;
+               File file = new File(filePath);
+               if (!file.delete())
+                       logger.error("Could not delete file " + filePath);
+       }
+
        public void createTag(final Long userId, final Long fileHeaderId, final String tag) throws ObjectNotFoundException {
                if (userId == null)
                        throw new ObjectNotFoundException("No user specified");
@@ -595,34 +652,55 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
 
                final User user = dao.getEntityById(User.class, userId);
                final FileHeader fh = dao.getEntityById(FileHeader.class, fileHeaderId);
+               final Folder parent = fh.getFolder();
+               if (parent == null)
+                       throw new ObjectNotFoundException("The specified file has no parent folder");
                user.addTag(fh, tag);
+               touchParentFolders(parent, user, new Date());
        }
 
-       /* (non-Javadoc)
-        * @see gr.ebs.gss.server.ejb.ExternalAPI#getUserTags(java.lang.Long)
-        */
-       @WebMethod(operationName = "getUserTags")
        public Set<String> getUserTags(final Long userId) throws ObjectNotFoundException {
                return dao.getUserTags(userId);
        }
 
-       /* (non-Javadoc)
-        * @see gr.ebs.gss.server.ejb.ExternalAPI#updateFile(java.lang.Long, java.lang.Long, java.lang.String, java.util.Set)
-        */
-       public void updateFile(Long userId, Long fileId, String name, String tagSet) throws ObjectNotFoundException, InsufficientPermissionsException {
+       public void updateFile(Long userId, Long fileId, String name,
+                               String tagSet, Date modificationDate, Boolean versioned,
+                               Boolean readForAll,     Set<PermissionDTO> permissions)
+                       throws DuplicateNameException, ObjectNotFoundException, InsufficientPermissionsException {
                if (userId == null)
                        throw new ObjectNotFoundException("No user specified");
                if (fileId == null)
                        throw new ObjectNotFoundException("No file specified");
                FileHeader file = dao.getEntityById(FileHeader.class, fileId);
-               User user = dao.getEntityById(User.class, userId);
-               if (!file.hasWritePermission(user))
-                       throw new InsufficientPermissionsException("User " + user.getId() + " cannot update file " + file.getName() + "(" + file.getId() + ")");
+               final Folder parent = file.getFolder();
+               if (parent == null)
+                       throw new ObjectNotFoundException("The specified file has no parent folder");
 
-               if (name != null)
+               User user = dao.getEntityById(User.class, userId);
+               // Check permissions for modifying the file metadata.
+               if ((name != null || tagSet != null || modificationDate != null || versioned != null) && !file.hasWritePermission(user))
+                       throw new InsufficientPermissionsException("User " + user.getId() +     " cannot update file " + file.getName() + "(" + file.getId() + ")");
+               // Check permissions for making file public.
+               if (readForAll != null && !user.equals(file.getOwner()))
+                               throw new InsufficientPermissionsException("Only the owner can make a file public or not public");
+               // Check permissions for modifying the ACL.
+               if(permissions != null && !permissions.isEmpty() &&     !file.hasModifyACLPermission(user))
+                       throw new InsufficientPermissionsException("User " + user.getId() +     " cannot update the permissions on file " +     file.getName() + "(" + file.getId() + ")");
+
+               if (name != null) {
+                       // Do plain check for file already exists.
+                       // Extreme concurrency case should be caught by constraint violation later.
+                       if (dao.existsFolderOrFile(parent.getId(), name)) throw new DuplicateNameException("A file or folder with the name '" + name + "' already exists");
                        file.setName(name);
-               List<FileTag> tags = file.getFileTags();
+               }
 
+               if (modificationDate != null)
+                       file.getAuditInfo().setModificationDate(modificationDate);
+               else
+                       file.getAuditInfo().setModificationDate(new Date());
+               file.getAuditInfo().setModifiedBy(user);
+
+               List<FileTag> tags = file.getFileTags();
                if (tagSet != null) {
                        Iterator<FileTag> i = tags.iterator();
                        while (i.hasNext()) {
@@ -637,6 +715,32 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                        while (st.hasMoreTokens())
                                new FileTag(user, file, st.nextToken().trim());
                }
+               if (versioned != null && !file.isVersioned() == versioned) {
+                       if (file.isVersioned())
+                               removeOldVersions(userId, fileId);
+                       file.setVersioned(versioned);
+               }
+               if (readForAll != null && user.equals(file.getOwner()))
+                       file.setReadForAll(readForAll);
+               if (permissions != null && !permissions.isEmpty())
+                       setFilePermissions(file, permissions);
+
+               /*
+                * Force constraint violation to manifest itself here.
+                * This should cover extreme concurrency cases that the simple check
+                * above hasn't caught.
+                */
+               try {
+                       dao.flush();
+               }
+               catch (EJBTransactionRolledbackException e) {
+                       Throwable cause = e.getCause();
+                       if (cause instanceof PersistenceException && cause.getCause() instanceof ConstraintViolationException)
+                               throw new DuplicateNameException("A file or folder with the name '" + name + "' already exists");
+                       throw e;
+               }
+
+               touchParentFolders(parent, user, new Date());
 
                // Re-index the file if it was modified.
                if (name != null || tagSet != null)
@@ -818,7 +922,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                        // Supply a more accurate problem description.
                        throw new GSSIOException("Problem creating file",ioe);
                }
-               return updateFileContents(userId, fileId, mimeType, file);
+               return updateFileContents(userId, fileId, mimeType, file.length(), file.getAbsolutePath());
        }
 
        @Override
@@ -871,6 +975,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                User user = dao.getEntityById(User.class, userId);
                if (!file.hasReadPermission(user) || !destination.hasWritePermission(user))
                        throw new InsufficientPermissionsException("You don't have the necessary permissions");
+               boolean versioned = file.isVersioned();
                int versionsNumber = file.getBodies().size();
                FileBody oldestBody = file.getBodies().get(0);
                assert oldestBody != null;
@@ -878,6 +983,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                try {
                        createFile(user.getId(), destination.getId(), destName, oldestBody.getMimeType(), new FileInputStream(contents));
                        FileHeader copiedFile = dao.getFile(destination.getId(), destName);
+                       copiedFile.setVersioned(versioned);
                        dao.flush();
                        if (versionsNumber > 1)
                                for (int i = 1; i < versionsNumber; i++) {
@@ -1050,10 +1156,11 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
 
                file.setDeleted(true);
                dao.update(file);
+               touchParentFolders(parent, user, new Date());
        }
 
        @Override
-       public void moveFileToPath(Long userId, Long ownerId, Long fileId, String dest) throws ObjectNotFoundException, InsufficientPermissionsException, DuplicateNameException, GSSIOException, QuotaExceededException {
+       public void moveFileToPath(Long userId, Long ownerId, Long fileId, String dest) throws ObjectNotFoundException, InsufficientPermissionsException, QuotaExceededException {
                if (userId == null)
                        throw new ObjectNotFoundException("No user specified");
                if (ownerId == null)
@@ -1071,7 +1178,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
        }
 
        @Override
-       public void moveFile(Long userId, Long fileId, Long destId, String destName) throws InsufficientPermissionsException, ObjectNotFoundException, DuplicateNameException, GSSIOException, QuotaExceededException {
+       public void moveFile(Long userId, Long fileId, Long destId, String destName) throws InsufficientPermissionsException, ObjectNotFoundException, QuotaExceededException {
                if (userId == null)
                        throw new ObjectNotFoundException("No user specified");
                if (fileId == null)
@@ -1082,25 +1189,46 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                        throw new ObjectNotFoundException("No destination file name specified");
 
                FileHeader file = dao.getEntityById(FileHeader.class, fileId);
+               Folder source = file.getFolder();
                Folder destination = dao.getEntityById(Folder.class, destId);
 
                User owner = dao.getEntityById(User.class, userId);
                if (!file.hasDeletePermission(owner) || !destination.hasWritePermission(owner))
                        throw new InsufficientPermissionsException("User " + owner.getId() + " cannot move file " + file.getName() + "(" + file.getId() + ")");
-               FileBody body = file.getCurrentBody();
-               assert body != null;
-               File contents = new File(body.getStoredFilePath());
-               try {
-                       createFile(owner.getId(), destination.getId(), destName, body.getMimeType(), new FileInputStream(contents));
-               } catch (FileNotFoundException e) {
-                       throw new ObjectNotFoundException("File contents not found for file " + body.getStoredFilePath());
-               }
-               deleteFile(userId, fileId);
 
+               // if the destination folder belongs to another user:
+               if (!file.getOwner().equals(destination.getOwner())) {
+                       // (a) check if the destination quota allows the move
+                       if(getQuotaLeft(destination.getOwner().getId()) < file.getTotalSize())
+                               throw new QuotaExceededException("Not enough free space available");
+                       User newOwner = destination.getOwner();
+                       // (b) if quota OK, change the owner of the file
+                       file.setOwner(newOwner);
+                       // if the file has no permission for the new owner, add it
+                       Permission ownerPermission = null;
+                       for (final Permission p : file.getPermissions())
+                               if (p.getUser() != null)
+                                       if (p.getUser().equals(newOwner)) {
+                                               ownerPermission = p;
+                                               break;
+                                       }
+                       if (ownerPermission == null) {
+                               ownerPermission = new Permission();
+                               ownerPermission.setUser(newOwner);
+                               file.addPermission(ownerPermission);
+                       }
+                       ownerPermission.setRead(true);
+                       ownerPermission.setWrite(true);
+                       ownerPermission.setModifyACL(true);
+               }
+               // move the file to the destination folder
+               file.setFolder(destination);
+               touchParentFolders(source, owner, new Date());
+               touchParentFolders(destination, owner, new Date());
        }
 
        @Override
-       public void moveFolderToPath(Long userId, Long ownerId, Long folderId, String dest) throws ObjectNotFoundException, DuplicateNameException, InsufficientPermissionsException, GSSIOException, QuotaExceededException {
+       public void moveFolderToPath(Long userId, Long ownerId, Long folderId, String dest) throws ObjectNotFoundException, InsufficientPermissionsException, QuotaExceededException {
                if (userId == null)
                        throw new ObjectNotFoundException("No user specified");
                if (ownerId == null)
@@ -1118,16 +1246,62 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
        }
 
        @Override
-       public void moveFolder(Long userId, Long folderId, Long destId, String destName) throws ObjectNotFoundException, DuplicateNameException, InsufficientPermissionsException, GSSIOException, QuotaExceededException {
-               // TODO Simple Move and delete of original folder, in production
-               // scenario we must first check individual files and folders permissions
-               copyFolderStructure(userId, folderId, destId, destName);
-               deleteFolder(userId, folderId);
+       public void moveFolder(Long userId, Long folderId, Long destId, String destName)
+                       throws ObjectNotFoundException, InsufficientPermissionsException,
+                       QuotaExceededException {
+               Folder source = dao.getEntityById(Folder.class, folderId);
+               Folder destination = dao.getEntityById(Folder.class, destId);
+               User user = dao.getEntityById(User.class, userId);
+               User sourceOwner = source.getOwner();
+               User destinationOwner = destination.getOwner();
+               // Do not move trashed folders and contents.
+               if (source.isDeleted())
+                       return;
+               // Check permissions.
+               if (!destination.hasWritePermission(user)
+                               || !source.hasReadPermission(user)
+                               || !source.hasWritePermission(user))
+                       throw new InsufficientPermissionsException("You don't have the " +
+                                       "necessary permissions");
+               // Use the same timestamp for all subsequent modifications to make
+               // changes appear simultaneous.
+               Date now = new Date();
+               // If source and destination are not in the same user's namespace,
+               // change owners and check quota.
+               if (!sourceOwner.equals(destinationOwner)) {
+                       changeOwner(source, destinationOwner, user, now);
+                       if (getQuotaLeft(destinationOwner.getId()) < 0)
+                               throw new QuotaExceededException("Not enough free space " +
+                                               "available in destination folder");
+               }
+               // Perform the move.
+               Folder oldParent = source.getParent();
+               oldParent.removeSubfolder(source);
+               destination.addSubfolder(source);
+               // Mark the former parent and destination trees upwards as modified.
+               touchParentFolders(oldParent, user, now);
+               touchParentFolders(source, user, now);
        }
 
-       /* (non-Javadoc)
-        * @see gr.ebs.gss.server.ejb.ExternalAPI#getDeletedFiles(java.lang.Long)
+       /**
+        * Recursively change the owner of the specified folder and all of its
+        * contents to the specified owner. Also mark them all as modified with the
+        * specified modifier and modificationDate.
         */
+       private void changeOwner(Folder folder, User owner, User modifier, Date modificationDate) {
+               for (FileHeader file: folder.getFiles()) {
+                       file.setOwner(owner);
+                       file.getAuditInfo().setModificationDate(modificationDate);
+                       file.getAuditInfo().setModifiedBy(modifier);
+               }
+               for (Folder sub: folder.getSubfolders())
+                       changeOwner(sub, owner, modifier, modificationDate);
+               folder.setOwner(owner);
+               folder.getAuditInfo().setModificationDate(modificationDate);
+               folder.getAuditInfo().setModifiedBy(modifier);
+       }
+
+       @Override
        public List<FileHeaderDTO> getDeletedFiles(Long userId) throws ObjectNotFoundException {
                // Validate.
                if (userId == null)
@@ -1161,6 +1335,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
 
                file.setDeleted(false);
                dao.update(file);
+               touchParentFolders(parent, user, new Date());
        }
 
        @Override
@@ -1175,6 +1350,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                        throw new InsufficientPermissionsException("You don't have the necessary permissions");
                folder.setDeleted(true);
                dao.update(folder);
+               touchParentFolders(folder, user, new Date());
                for (FileHeader file : folder.getFiles())
                        moveFileToTrash(userId, file.getId());
                for (Folder subFolder : folder.getSubfolders())
@@ -1200,6 +1376,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                for (Folder subFolder : folder.getSubfolders())
                        removeFolderFromTrash(userId, subFolder.getId());
                dao.update(folder);
+               touchParentFolders(folder, user, new Date());
        }
 
        @Override
@@ -1232,7 +1409,8 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
        }
 
        @Override
-       public User createUser(String username, String name, String mail) throws ObjectNotFoundException {
+       public User createUser(String username, String name, String mail,
+                               String idp, String idpid) throws ObjectNotFoundException {
                if (username == null)
                        throw new ObjectNotFoundException("No username specified");
                if (name == null)
@@ -1242,12 +1420,15 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                user.setUsername(username);
                user.setName(name);
                user.setEmail(mail);
+               user.setIdentityProvider(idp);
+               user.setIdentityProviderId(idpid);
                Date now = new Date();
                AuditInfo auditInfo = new AuditInfo();
                auditInfo.setCreationDate(now);
                auditInfo.setModificationDate(now);
                user.setAuditInfo(auditInfo);
                user.generateAuthToken();
+               user.generateWebDAVPassword();
                dao.create(user);
                // Make sure we get an ID in the user object.
                dao.flush();
@@ -1267,17 +1448,6 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
        }
 
        @Override
-       public User updateUser(String username, String name, String mail) throws ObjectNotFoundException {
-               if (username == null)
-                       throw new ObjectNotFoundException("No username specified");
-
-               User user = dao.getUser(username);
-               user.setName(name);
-               user.setEmail(mail);
-               return user;
-       }
-
-       @Override
        public User findUser(String username) {
                if (username == null)
                        return null;
@@ -1319,30 +1489,37 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
 
        }
 
-       /* (non-Javadoc)
-        * @see gr.ebs.gss.server.ejb.ExternalAPI#setFolderPermissions(java.lang.Long, java.lang.Long, java.util.Set)
+       /**
+        * Set the provided permissions as the new permissions of the specified
+        * folder.
+        *
+        * @param user
+        * @param folder
+        * @param permissions
+        * @throws ObjectNotFoundException
+        * @throws InsufficientPermissionsException
         */
-       @Override
-       public void setFolderPermissions(Long userId, Long folderId, Set<PermissionDTO> permissions) throws ObjectNotFoundException, InsufficientPermissionsException {
-               if (userId == null)
-                       throw new ObjectNotFoundException("No user specified");
-               if (folderId == null)
-                       throw new ObjectNotFoundException("No folder specified");
-               User user = dao.getEntityById(User.class, userId);
-               Folder folder = dao.getEntityById(Folder.class, folderId);
-               if(!folder.hasModifyACLPermission(user))
-                       throw new InsufficientPermissionsException("You don't have the necessary permissions");
+       private void setFolderPermissions(User user, Folder folder, Set<PermissionDTO> permissions) throws ObjectNotFoundException, InsufficientPermissionsException {
+               // Delete previous entries
+               for (Permission perm: folder.getPermissions())
+                       dao.delete(perm);
                folder.getPermissions().clear();
                for (PermissionDTO dto : permissions) {
                        if (dto.getUser()!=null && dto.getUser().getId().equals(folder.getOwner().getId()) && (!dto.hasRead() || !dto.hasWrite() || !dto.hasModifyACL()))
                                        throw new InsufficientPermissionsException("Can't remove permissions from owner");
+                       // Don't include 'empty' permission
+                       if (!dto.getRead() && !dto.getWrite() && !dto.getModifyACL()) continue;
                        folder.addPermission(getPermission(dto));
                }
                dao.update(folder);
-               for (FileHeader fh : folder.getFiles())
-                       setFilePermissions(userId, fh.getId(), fh.isReadForAll(), permissions);
+               for (FileHeader file : folder.getFiles()) {
+                       setFilePermissions(file, permissions);
+                       Date now = new Date();
+                       file.getAuditInfo().setModificationDate(now);
+                       file.getAuditInfo().setModifiedBy(user);
+               }
                for (Folder sub : folder.getSubfolders())
-                       setFolderPermissions(userId, sub.getId(), permissions);
+                       setFolderPermissions(user, sub, permissions);
        }
 
        private Permission getPermission(PermissionDTO dto) throws ObjectNotFoundException {
@@ -1440,9 +1617,6 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
 
        }
 
-       /* (non-Javadoc)
-        * @see gr.ebs.gss.server.ejb.ExternalAPI#getUsersSharingFoldersForUser(java.lang.Long)
-        */
        @Override
        public List<UserDTO> getUsersSharingFoldersForUser(Long userId) throws ObjectNotFoundException {
                List<User> users = dao.getUsersSharingFoldersForUser(userId);
@@ -1456,9 +1630,6 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                return res;
        }
 
-       /* (non-Javadoc)
-        * @see gr.ebs.gss.server.ejb.ExternalAPI#getFilePermissions(java.lang.Long, java.lang.Long)
-        */
        @Override
        public Set<PermissionDTO> getFilePermissions(Long userId, Long fileId) throws ObjectNotFoundException, InsufficientPermissionsException {
                if (userId == null)
@@ -1481,37 +1652,33 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                return result;
        }
 
-       @Override
-       public void setFilePermissions(Long userId, Long fileId, Boolean readForAll, Set<PermissionDTO> permissions) throws ObjectNotFoundException, InsufficientPermissionsException {
-               if (userId == null)
-                       throw new ObjectNotFoundException("No user specified");
-               if (fileId == null)
-                       throw new ObjectNotFoundException("No folder specified");
-
-               User user = dao.getEntityById(User.class, userId);
-               FileHeader file = dao.getEntityById(FileHeader.class, fileId);
-               if(!file.hasModifyACLPermission(user))
-                       throw new InsufficientPermissionsException("You don't have the necessary permissions");
-
-               if (readForAll != null)
-                       if (user.equals(file.getOwner()))
-                               file.setReadForAll(readForAll);
-                       else
-                               throw new InsufficientPermissionsException("Only the owner can change the read-for-all flag");
-
+       /**
+        * Set the provided permissions as the new permissions of the specified
+        * file. This method sets the modification date/user attributes to the
+        * current values as a side effect.
+        *
+        * @param file
+        * @param permissions
+        * @throws ObjectNotFoundException
+        * @throws InsufficientPermissionsException
+        */
+       private void setFilePermissions(FileHeader file,
+                               Set<PermissionDTO> permissions)
+                       throws ObjectNotFoundException, InsufficientPermissionsException {
                if (permissions != null && !permissions.isEmpty()) {
+                       // Delete previous entries.
+                       for (Permission perm: file.getPermissions())
+                               dao.delete(perm);
                        file.getPermissions().clear();
                        for (PermissionDTO dto : permissions) {
                                if (dto.getUser()!=null && dto.getUser().getId().equals(file.getOwner().getId()) && (!dto.hasRead() || !dto.hasWrite() || !dto.hasModifyACL()))
                                        throw new InsufficientPermissionsException("Can't remove permissions from owner");
+                               // Don't include 'empty' permission.
+                               if (!dto.getRead() && !dto.getWrite() && !dto.getModifyACL()) continue;
                                file.addPermission(getPermission(dto));
                        }
+                       dao.flush();
                }
-
-               // Update the file if there was a change.
-               if (readForAll != null || permissions != null && !permissions.isEmpty())
-                       dao.update(file);
-
        }
 
        @Override
@@ -1741,7 +1908,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
         * @see gr.ebs.gss.server.ejb.ExternalAPI#moveFiles(java.lang.Long, java.util.List, java.lang.Long)
         */
        @Override
-       public void moveFiles(Long userId, List<Long> fileIds, Long destId) throws InsufficientPermissionsException, ObjectNotFoundException, DuplicateNameException, GSSIOException, QuotaExceededException {
+       public void moveFiles(Long userId, List<Long> fileIds, Long destId) throws InsufficientPermissionsException, ObjectNotFoundException, QuotaExceededException {
                for(Long l : fileIds){
                        FileHeader file = dao.getEntityById(FileHeader.class, l);
                        moveFile(userId, l, destId, file.getName());
@@ -1757,7 +1924,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                if (userId == null)
                        throw new ObjectNotFoundException("No user specified");
                final User user = dao.getEntityById(User.class, userId);
-               List<File> filesToRemove = new ArrayList<File>();
+               List<String> filesToRemove = new ArrayList<String>();
                //first delete database objects
                for(Long fileId : fileIds){
                        if (fileId == null)
@@ -1770,16 +1937,14 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                                throw new InsufficientPermissionsException("User " + user.getId() + " cannot delete file " + file.getName() + "(" + file.getId() + ")");
 
                        parent.removeFile(file);
-                       for (final FileBody body : file.getBodies()) {
-                               final File fileContents = new File(body.getStoredFilePath());
-                               filesToRemove.add(fileContents);
-                       }
+                       for (final FileBody body : file.getBodies())
+                               filesToRemove.add(body.getStoredFilePath());
                        dao.delete(file);
+                       touchParentFolders(parent, user, new Date());
                }
                //then remove physical files if everything is ok
-               for(File physicalFile : filesToRemove)
-                       if (!physicalFile.delete())
-                               logger.error("Could not delete file " + physicalFile.getPath());
+               for(String physicalFileName : filesToRemove)
+                       deleteActualFile(physicalFileName);
                //then unindex deleted files
                for(Long fileId : fileIds)
                        indexFile(fileId, true);
@@ -1899,11 +2064,11 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                                if(b.getVersion() == body.getVersion()-1)
                                        header.setCurrentBody(b);
                }
-               final File fileContents = new File(body.getStoredFilePath());
-               if (!fileContents.delete())
-                       logger.error("Could not delete file " + body.getStoredFilePath());
+               deleteActualFile(body.getStoredFilePath());
                header.getBodies().remove(body);
 
+               Folder parent = header.getFolder();
+               touchParentFolders(parent, user, new Date());
 
        }
 
@@ -1945,36 +2110,15 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                while(it.hasNext()){
                        FileBody body = it.next();
                        if(!body.equals(header.getCurrentBody())){
-                               final File fileContents = new File(body.getStoredFilePath());
-                               if (!fileContents.delete())
-                                       logger.error("Could not delete file " + body.getStoredFilePath());
+                               deleteActualFile(body.getStoredFilePath());
                                it.remove();
                                dao.delete(body);
                        }
                }
                header.getCurrentBody().setVersion(1);
 
-       }
-
-       /* (non-Javadoc)
-        * @see gr.ebs.gss.server.ejb.ExternalAPI#toggleFileVersioning(java.lang.Long, java.lang.Long, boolean)
-        */
-       @Override
-       public void toggleFileVersioning(Long userId, Long fileId, boolean versioned) throws ObjectNotFoundException, InsufficientPermissionsException {
-               if (userId == null)
-                       throw new ObjectNotFoundException("No user specified");
-               if (fileId == null)
-                       throw new ObjectNotFoundException("No file specified");
-               User user = dao.getEntityById(User.class, userId);
-               FileHeader header = dao.getEntityById(FileHeader.class, fileId);
-               if(!header.hasWritePermission(user))
-                       throw new InsufficientPermissionsException("You don't have the necessary permissions");
-               if(!header.isVersioned() == versioned){
-                       if(header.isVersioned())
-                               removeOldVersions(userId, fileId);
-                       header.setVersioned(versioned);
-
-               }
+               Folder parent = header.getFolder();
+               touchParentFolders(parent, user, new Date());
        }
 
        /**
@@ -2128,7 +2272,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                }
        }
 
-       public FileHeaderDTO createFile(Long userId, Long folderId, String name, String mimeType, File fileObject)
+       public FileHeaderDTO createFile(Long userId, Long folderId, String name, String mimeType, long fileSize, String filePath)
                        throws DuplicateNameException, ObjectNotFoundException, GSSIOException,
                        InsufficientPermissionsException, QuotaExceededException {
                // Validate.
@@ -2184,10 +2328,11 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
 
                // Create the file body.
                try {
-                       createFileBody(name, contentType, fileObject, file, auditInfo, owner);
+                       createFileBody(name, contentType, fileSize, filePath, file, auditInfo);
                } catch (FileNotFoundException e) {
                        throw new GSSIOException(e);
                }
+               touchParentFolders(parent, owner, new Date());
                dao.flush();
                indexFile(file.getId(), false);
 
@@ -2197,7 +2342,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
        /* (non-Javadoc)
         * @see gr.ebs.gss.server.ejb.ExternalAPI#updateFileContents(java.lang.Long, java.lang.Long, java.lang.String, java.io.InputStream)
         */
-       public FileHeaderDTO updateFileContents(Long userId, Long fileId, String mimeType, File fileObject) throws ObjectNotFoundException, GSSIOException, InsufficientPermissionsException, QuotaExceededException {
+       public FileHeaderDTO updateFileContents(Long userId, Long fileId, String mimeType, long fileSize, String filePath) throws ObjectNotFoundException, GSSIOException, InsufficientPermissionsException, QuotaExceededException {
                if (userId == null)
                        throw new ObjectNotFoundException("No user specified");
                if (fileId == null)
@@ -2222,10 +2367,12 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                auditInfo.setModifiedBy(owner);
                auditInfo.setModificationDate(now);
                try {
-                       createFileBody(file.getName(), contentType, fileObject, file, auditInfo, owner);
+                       createFileBody(file.getName(), contentType, fileSize, filePath, file, auditInfo);
                } catch (FileNotFoundException e) {
                        throw new GSSIOException(e);
                }
+               Folder parent = file.getFolder();
+               touchParentFolders(parent, owner, new Date());
 
                indexFile(fileId, false);
                return file.getDTO();
@@ -2239,7 +2386,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
         */
        private String identifyMimeType(String filename) {
                if (filename.indexOf('.') != -1) {
-                       String extension = filename.substring(filename.lastIndexOf('.'));
+                       String extension = filename.substring(filename.lastIndexOf('.')).toLowerCase(Locale.ENGLISH);
                        if (".doc".equals(extension))
                                return "application/msword";
                        else if (".xls".equals(extension))
@@ -2248,6 +2395,16 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                                return "application/vnd.ms-powerpoint";
                        else if (".pdf".equals(extension))
                                return "application/pdf";
+                       else if (".gif".equals(extension))
+                               return "image/gif";
+                       else if (".jpg".equals(extension) || ".jpeg".equals(extension) || ".jpe".equals(extension))
+                               return "image/jpeg";
+                       else if (".tiff".equals(extension) || ".tif".equals(extension))
+                               return "image/tiff";
+                       else if (".png".equals(extension))
+                               return "image/png";
+                       else if (".bmp".equals(extension))
+                               return "image/bmp";
                }
                // when all else fails assign the default mime type
                return DEFAULT_MIME_TYPE;
@@ -2259,23 +2416,25 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
         *
         * @param name the original file name
         * @param mimeType the content type
-        * @param uploadedFile the uploaded file contents
+        * @param fileSize the uploaded file size
+        * @param filePath the uploaded file full path
         * @param header the file header that will be associated with the new body
         * @param auditInfo the audit info
         * @param owner the owner of the file
         * @throws FileNotFoundException
         * @throws QuotaExceededException
         */
-       private void createFileBody(String name, String mimeType, File uploadedFile,
-                               FileHeader header, AuditInfo auditInfo, User owner)
+       private void createFileBody(String name, String mimeType, long fileSize, String filePath,
+                               FileHeader header, AuditInfo auditInfo)
                        throws FileNotFoundException, QuotaExceededException {
 
                long currentTotalSize = 0;
                if (!header.isVersioned() && header.getCurrentBody() != null && header.getBodies() != null)
                        currentTotalSize = header.getTotalSize();
                Long quotaLeft = getQuotaLeft(header.getOwner().getId());
-               if(quotaLeft < uploadedFile.length()-currentTotalSize) {
-                       uploadedFile.delete();
+               if(quotaLeft < fileSize-currentTotalSize) {
+                       // quota exceeded -> delete the file
+                       deleteActualFile(filePath);
                        throw new QuotaExceededException("Not enough free space available");
                }
 
@@ -2289,9 +2448,9 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                else
                        body.setMimeType(mimeType);
                body.setAuditInfo(auditInfo);
-               body.setFileSize(uploadedFile.length());
+               body.setFileSize(fileSize);
                body.setOriginalFilename(name);
-               body.setStoredFilePath(uploadedFile.getAbsolutePath());
+               body.setStoredFilePath(filePath);
                //CLEAR OLD VERSION IF FILE IS NOT VERSIONED AND GETS UPDATED
                if(!header.isVersioned() && header.getCurrentBody() != null){
                        header.setCurrentBody(null);
@@ -2299,9 +2458,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                                Iterator<FileBody> it = header.getBodies().iterator();
                                while(it.hasNext()){
                                        FileBody bo = it.next();
-                                       File fileContents = new File(bo.getStoredFilePath());
-                                       if (!fileContents.delete())
-                                               logger.error("Could not delete file " + bo.getStoredFilePath());
+                                       deleteActualFile(bo.getStoredFilePath());
                                        it.remove();
                                        dao.delete(bo);
                                }
@@ -2310,6 +2467,7 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
 
                dao.flush();
                header.addBody(body);
+               header.setAuditInfo(auditInfo);
 
                dao.create(body);
        }
@@ -2466,4 +2624,48 @@ public class ExternalAPIBean implements ExternalAPI, ExternalAPIRemote {
                return true;
        }
 
+       @Override
+       public String resetWebDAVPassword(Long userId) throws ObjectNotFoundException {
+               if (userId == null)
+                       throw new ObjectNotFoundException("No user specified");
+               User user = dao.getEntityById(User.class, userId);
+               user.generateWebDAVPassword();
+               return user.getWebDAVPassword();
+       }
+
+       @Override
+       public Invitation findInvite(String code) {
+               if (code == null)
+                       return null;
+               return dao.findInvite(code);
+       }
+
+       @Override
+       public void createLdapUser(String username, String firstname, String lastname, String email, String password) {
+               LDAPConnection lc = new LDAPConnection();
+        LDAPAttributeSet attributeSet = new LDAPAttributeSet();
+        attributeSet.add(new LDAPAttribute("objectClass", getConfiguration().getStringArray("objectClass")));
+        attributeSet.add(new LDAPAttribute("uid", username));
+        attributeSet.add(new LDAPAttribute("cn", new String[]{firstname + " " + lastname}));
+        attributeSet.add(new LDAPAttribute("sn", lastname));
+        attributeSet.add(new LDAPAttribute("givenName", firstname));
+        attributeSet.add(new LDAPAttribute("mail", email));
+        attributeSet.add(new LDAPAttribute("userPassword", password));
+        String dn = "uid=" + username + "," + getConfiguration().getString("baseDn");
+        LDAPEntry newEntry = new LDAPEntry(dn, attributeSet);
+        try {
+               lc.connect(getConfiguration().getString("ldapHost"), LDAPConnection.DEFAULT_PORT);
+               lc.bind(LDAPConnection.LDAP_V3, getConfiguration().getString("bindDn"),
+                               getConfiguration().getString("bindPassword").getBytes("UTF8"));
+               lc.add(newEntry);
+               logger.info("Successfully added LDAP account: " + dn);
+               lc.disconnect();
+        } catch(LDAPException e) {
+               throw new RuntimeException(e);
+        } catch(UnsupportedEncodingException e) {
+               throw new RuntimeException(e);
+        }
+
+       }
+
 }