package com.stacksync.syncservice.handler; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; import org.apache.log4j.Logger; import com.stacksync.commons.exceptions.ShareProposalNotCreatedException; import com.stacksync.commons.exceptions.UserNotFoundException; import com.stacksync.commons.models.Chunk; import com.stacksync.commons.models.CommitInfo; import com.stacksync.commons.models.Device; import com.stacksync.commons.models.Item; import com.stacksync.commons.models.ItemMetadata; import com.stacksync.commons.models.ItemVersion; import com.stacksync.commons.models.User; import com.stacksync.commons.models.UserWorkspace; import com.stacksync.commons.models.Workspace; import com.stacksync.commons.notifications.CommitNotification; import com.stacksync.syncservice.db.ConnectionPool; import com.stacksync.syncservice.db.DAOFactory; import com.stacksync.syncservice.db.DeviceDAO; import com.stacksync.syncservice.db.ItemDAO; import com.stacksync.syncservice.db.ItemVersionDAO; import com.stacksync.syncservice.db.UserDAO; import com.stacksync.syncservice.db.WorkspaceDAO; import com.stacksync.syncservice.exceptions.CommitExistantVersion; import com.stacksync.syncservice.exceptions.CommitWrongVersion; import com.stacksync.syncservice.exceptions.InternalServerError; import com.stacksync.syncservice.exceptions.dao.DAOException; import com.stacksync.syncservice.exceptions.dao.NoResultReturnedDAOException; import com.stacksync.syncservice.exceptions.storage.NoStorageManagerAvailable; import com.stacksync.syncservice.exceptions.storage.ObjectNotFoundException; import com.stacksync.syncservice.storage.StorageFactory; import com.stacksync.syncservice.storage.StorageManager; import com.stacksync.syncservice.storage.StorageManager.StorageType; import com.stacksync.syncservice.util.Config; public class Handler { private static final Logger logger = Logger.getLogger(Handler.class.getName()); protected Connection connection; protected WorkspaceDAO workspaceDAO; protected UserDAO userDao; protected DeviceDAO deviceDao; protected ItemDAO itemDao; protected ItemVersionDAO itemVersionDao; protected StorageManager storageManager; public enum Status { NEW, DELETED, CHANGED, RENAMED, MOVED }; public Handler(ConnectionPool pool) throws SQLException, NoStorageManagerAvailable { connection = pool.getConnection(); String dataSource = Config.getDatasource(); DAOFactory factory = new DAOFactory(dataSource); workspaceDAO = factory.getWorkspaceDao(connection); deviceDao = factory.getDeviceDAO(connection); userDao = factory.getUserDao(connection); itemDao = factory.getItemDAO(connection); itemVersionDao = factory.getItemVersionDAO(connection); StorageType type; if (Config.getSwiftKeystoneProtocol().equals("http")) { type = StorageType.SWIFT; } else { type = StorageType.SWIFT_SSL; } storageManager = StorageFactory.getStorageManager(type); } public CommitNotification doCommit(User user, Workspace workspace, Device device, List<ItemMetadata> items) throws DAOException { HashMap<Long, Long> tempIds = new HashMap<Long, Long>(); workspace = workspaceDAO.getById(workspace.getId()); // TODO: check if the workspace belongs to the user or its been given // access device = deviceDao.get(device.getId()); // TODO: check if the device belongs to the user user = userDao.findById(user.getId()); List<CommitInfo> responseObjects = new ArrayList<CommitInfo>(); for (ItemMetadata item : items) { ItemMetadata objectResponse = null; boolean committed; try { if (item.getParentId() != null) { Long parentId = tempIds.get(item.getParentId()); if (parentId != null) { item.setParentId(parentId); } } // if the item does not have ID but has a TempID, maybe it was // set if (item.getId() == null && item.getTempId() != null) { Long newId = tempIds.get(item.getTempId()); if (newId != null) { item.setId(newId); } } if (workspace.isShared()) { User owner = userDao.findById(workspace.getOwner().getId()); this.commitObject(owner, item, workspace, device); } else { this.commitObject(user, item, workspace, device); } if (item.getTempId() != null) { tempIds.put(item.getTempId(), item.getId()); } objectResponse = item; committed = true; } catch (CommitWrongVersion e) { logger.info("Commit wrong version item:" + e.getItem().getId()); Item serverObject = e.getItem(); objectResponse = this.getCurrentServerVersion(serverObject); committed = false; } catch (CommitExistantVersion e) { logger.info("Commit existant version item:" + e.getItem().getId()); Item serverObject = e.getItem(); objectResponse = this.getCurrentServerVersion(serverObject); committed = true; } catch (DAOException e) { logger.info("Owner of shared workspace not found:" + e); committed = false; } responseObjects.add(new CommitInfo(item.getVersion(), committed, objectResponse)); } return new CommitNotification(null, responseObjects, user.getQuotaLimit(), user.getQuotaUsedLogical()); } public Workspace doShareFolder(User user, List<String> emails, Item item, boolean isEncrypted) throws ShareProposalNotCreatedException, UserNotFoundException { // Check the owner try { user = userDao.findById(user.getId()); } catch (NoResultReturnedDAOException e) { logger.warn(e); throw new UserNotFoundException(e); } catch (DAOException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } // Get folder metadata try { item = itemDao.findById(item.getId()); } catch (DAOException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } if (item == null || !item.isFolder()) { throw new ShareProposalNotCreatedException("No folder found with the given ID."); } // Get the source workspace Workspace sourceWorkspace; try { sourceWorkspace = workspaceDAO.getById(item.getWorkspace().getId()); } catch (DAOException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } if (sourceWorkspace == null) { throw new ShareProposalNotCreatedException("Workspace not found."); } // Check the addressees List<User> addressees = new ArrayList<User>(); for (String email : emails) { User addressee; try { addressee = userDao.getByEmail(email); if (!addressee.getId().equals(user.getId())) { addressees.add(addressee); } } catch (IllegalArgumentException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } catch (DAOException e) { logger.warn(String.format("Email '%s' does not correspond with any user. ", email), e); } } if (addressees.isEmpty()) { throw new ShareProposalNotCreatedException("No addressees found"); } Workspace workspace; if (sourceWorkspace.isShared()) { workspace = sourceWorkspace; } else { // Create the new workspace String container = UUID.randomUUID().toString(); workspace = new Workspace(); workspace.setShared(true); workspace.setEncrypted(isEncrypted); workspace.setName(item.getFilename()); workspace.setOwner(user); workspace.setUsers(addressees); workspace.setSwiftContainer(container); workspace.setSwiftUrl(Config.getSwiftUrl() + "/" + user.getSwiftAccount()); // Create container in Swift try { storageManager.createNewWorkspace(workspace); } catch (Exception e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } // Save the workspace to the DB try { workspaceDAO.add(workspace); // add the owner to the workspace workspaceDAO.addUser(user, workspace); } catch (DAOException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } // Grant user to container in Swift try { storageManager.grantUserToWorkspace(user, user, workspace); } catch (Exception e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } // Migrate files to new workspace List<String> chunks; try { chunks = itemDao.migrateItem(item.getId(), workspace.getId()); } catch (Exception e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } // Move chunks to new container for (String chunkName : chunks) { try { storageManager.copyChunk(sourceWorkspace, workspace, chunkName); storageManager.deleteChunk(sourceWorkspace, chunkName); } catch (ObjectNotFoundException e) { logger.error(String.format( "Chunk %s not found in container %s. Could not migrate to container %s.", chunkName, sourceWorkspace.getSwiftContainer(), workspace.getSwiftContainer()), e); } catch (Exception e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } } } // Add the addressees to the workspace for (User addressee : addressees) { try { workspaceDAO.addUser(addressee, workspace); } catch (DAOException e) { workspace.getUsers().remove(addressee); logger.error(String.format("An error ocurred when adding the user '%s' to workspace '%s'", addressee.getId(), workspace.getId()), e); } // Grant the user to container in Swift try { storageManager.grantUserToWorkspace(user, addressee, workspace); } catch (Exception e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } } return workspace; } public UnshareData doUnshareFolder(User user, List<String> emails, Item item, boolean isEncrypted) throws ShareProposalNotCreatedException, UserNotFoundException { UnshareData response; // Check the owner try { user = userDao.findById(user.getId()); } catch (NoResultReturnedDAOException e) { logger.warn(e); throw new UserNotFoundException(e); } catch (DAOException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } // Get folder metadata try { item = itemDao.findById(item.getId()); } catch (DAOException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } if (item == null || !item.isFolder()) { throw new ShareProposalNotCreatedException("No folder found with the given ID."); } // Get the workspace Workspace sourceWorkspace; try { sourceWorkspace = workspaceDAO.getById(item.getWorkspace().getId()); } catch (DAOException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } if (sourceWorkspace == null) { throw new ShareProposalNotCreatedException("Workspace not found."); } if (!sourceWorkspace.isShared()) { throw new ShareProposalNotCreatedException("This workspace is not shared."); } // Check the addressees List<User> addressees = new ArrayList<User>(); for (String email : emails) { User addressee; try { addressee = userDao.getByEmail(email); if (addressee.getId().equals(sourceWorkspace.getOwner().getId())) { logger.warn(String.format("Email '%s' corresponds with owner of the folder. ", email)); throw new ShareProposalNotCreatedException("Email " + email + " corresponds with owner of the folder."); } if (!addressee.getId().equals(user.getId())) { addressees.add(addressee); } } catch (IllegalArgumentException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } catch (DAOException e) { logger.warn(String.format("Email '%s' does not correspond with any user. ", email), e); } } if (addressees.isEmpty()) { throw new ShareProposalNotCreatedException("No addressees found"); } // get workspace members List<UserWorkspace> workspaceMembers; try { workspaceMembers = doGetWorkspaceMembers(user, sourceWorkspace); } catch (InternalServerError e1) { throw new ShareProposalNotCreatedException(e1.toString()); } // remove users from workspace List<User> usersToRemove = new ArrayList<User>(); for (User userToRemove : addressees) { for (UserWorkspace member : workspaceMembers) { if (member.getUser().getEmail().equals(userToRemove.getEmail())) { workspaceMembers.remove(member); usersToRemove.add(userToRemove); break; } } } if (workspaceMembers.size() <= 1) { // All members have been removed from the workspace Workspace defaultWorkspace; try { // Always the last member of a shared folder should be the owner defaultWorkspace = workspaceDAO.getDefaultWorkspaceByUserId(sourceWorkspace.getOwner().getId()); } catch (DAOException e) { logger.error(e); throw new ShareProposalNotCreatedException("Could not get default workspace"); } // Migrate files to new workspace List<String> chunks; try { chunks = itemDao.migrateItem(item.getId(), defaultWorkspace.getId()); } catch (Exception e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } // Move chunks to new container for (String chunkName : chunks) { try { storageManager.copyChunk(sourceWorkspace, defaultWorkspace, chunkName); storageManager.deleteChunk(sourceWorkspace, chunkName); } catch (ObjectNotFoundException e) { logger.error(String.format( "Chunk %s not found in container %s. Could not migrate to container %s.", chunkName, sourceWorkspace.getSwiftContainer(), defaultWorkspace.getSwiftContainer()), e); } catch (Exception e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } } // delete workspace try { workspaceDAO.delete(sourceWorkspace.getId()); } catch (DAOException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } // delete container from swift try { storageManager.deleteWorkspace(sourceWorkspace); } catch (Exception e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } response = new UnshareData(usersToRemove, sourceWorkspace, true); } else { for (User userToRemove : usersToRemove) { try { workspaceDAO.deleteUser(userToRemove, sourceWorkspace); } catch (DAOException e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } try { storageManager.removeUserToWorkspace(user, userToRemove, sourceWorkspace); } catch (Exception e) { logger.error(e); throw new ShareProposalNotCreatedException(e); } } response = new UnshareData(usersToRemove, sourceWorkspace, false); } return response; } public List<UserWorkspace> doGetWorkspaceMembers(User user, Workspace workspace) throws InternalServerError { // TODO: check user permissions. List<UserWorkspace> members; try { members = workspaceDAO.getMembersById(workspace.getId()); } catch (DAOException e) { logger.error(e); throw new InternalServerError(e); } if (members == null || members.isEmpty()) { throw new InternalServerError("No members found in workspace."); } return members; } public Connection getConnection() { return this.connection; } /* * Private functions */ private void commitObject(User user, ItemMetadata item, Workspace workspace, Device device) throws CommitWrongVersion, CommitExistantVersion, DAOException { Item serverItem = itemDao.findById(item.getId()); // Check if this object already exists in the server. if (serverItem == null) { if (item.getVersion().equals(1L)) { long newQuotaUsedLogical = item.getSize() + user.getQuotaUsedLogical(); this.saveNewObject(item, workspace, device); // Update quota used user.setQuotaUsedLogical(newQuotaUsedLogical); userDao.updateAvailableQuota(user); } else { throw new CommitWrongVersion("Invalid version " + item.getVersion() + ". Expected version 1."); } return; } // Check if the client version already exists in the server long serverVersion = serverItem.getLatestVersion(); long clientVersion = item.getVersion(); boolean existVersionInServer = (serverVersion >= clientVersion); if (existVersionInServer) { this.saveExistentVersion(serverItem, item); } else { // Check if version is correct if (serverVersion + 1 == clientVersion) { ItemMetadata serverItemMetadata = itemDao.findById(item.getId(), false, serverItem.getLatestVersion(), false, false); if (item.getStatus().equals(Status.DELETED.toString())) { item.setSize(0L); } long newQuotaUsedLogical = user.getQuotaUsedLogical() + (item.getSize() - serverItemMetadata.getSize()); if (newQuotaUsedLogical < 0) { newQuotaUsedLogical = 0L; } this.saveNewVersion(item, serverItem, workspace, device); logger.debug("New Quota:" + newQuotaUsedLogical); user.setQuotaUsedLogical(newQuotaUsedLogical); userDao.updateAvailableQuota(user); } else { throw new CommitWrongVersion("Invalid version.", serverItem); } } } private void saveNewObject(ItemMetadata metadata, Workspace workspace, Device device) throws DAOException { // Create workspace and parent instances Long parentId = metadata.getParentId(); Item parent = null; if (parentId != null) { parent = itemDao.findById(parentId); } beginTransaction(); try { // Insert object to DB Item item = new Item(); item.setId(metadata.getId()); item.setFilename(metadata.getFilename()); item.setMimetype(metadata.getMimetype()); item.setIsFolder(metadata.isFolder()); item.setClientParentFileVersion(metadata.getParentVersion()); item.setLatestVersion(metadata.getVersion()); item.setWorkspace(workspace); item.setParent(parent); itemDao.put(item); // set the global ID metadata.setId(item.getId()); // Insert objectVersion ItemVersion objectVersion = new ItemVersion(); objectVersion.setVersion(metadata.getVersion()); objectVersion.setModifiedAt(metadata.getModifiedAt()); objectVersion.setChecksum(metadata.getChecksum()); objectVersion.setStatus(metadata.getStatus()); objectVersion.setSize(metadata.getSize()); objectVersion.setItem(item); objectVersion.setDevice(device); itemVersionDao.add(objectVersion); // If no folder, create new chunks and update the available quota if (!metadata.isFolder()) { long fileSize = metadata.getSize(); List<String> chunks = metadata.getChunks(); this.createChunks(chunks, objectVersion); } commitTransaction(); } catch (Exception e) { logger.error(e); rollbackTransaction(); } } private void saveNewVersion(ItemMetadata metadata, Item serverItem, Workspace workspace, Device device) throws DAOException { beginTransaction(); try { // Create new objectVersion ItemVersion itemVersion = new ItemVersion(); itemVersion.setVersion(metadata.getVersion()); itemVersion.setModifiedAt(metadata.getModifiedAt()); itemVersion.setChecksum(metadata.getChecksum()); itemVersion.setStatus(metadata.getStatus()); itemVersion.setSize(metadata.getSize()); itemVersion.setItem(serverItem); itemVersion.setDevice(device); itemVersionDao.add(itemVersion); // If no folder, create new chunks if (!metadata.isFolder()) { List<String> chunks = metadata.getChunks(); this.createChunks(chunks, itemVersion); } // TODO To Test!! String status = metadata.getStatus(); if (status.equals(Status.RENAMED.toString()) || status.equals(Status.MOVED.toString()) || status.equals(Status.DELETED.toString())) { serverItem.setFilename(metadata.getFilename()); Long parentFileId = metadata.getParentId(); if (parentFileId == null) { serverItem.setClientParentFileVersion(null); serverItem.setParent(null); } else { serverItem.setClientParentFileVersion(metadata.getParentVersion()); Item parent = itemDao.findById(parentFileId); serverItem.setParent(parent); } } // Update object latest version serverItem.setLatestVersion(metadata.getVersion()); itemDao.put(serverItem); commitTransaction(); } catch (Exception e) { logger.error(e); rollbackTransaction(); } } private void createChunks(List<String> chunksString, ItemVersion objectVersion) throws IllegalArgumentException, DAOException { if (chunksString != null) { if (chunksString.size() > 0) { List<Chunk> chunks = new ArrayList<Chunk>(); int i = 0; for (String chunkName : chunksString) { chunks.add(new Chunk(chunkName, i)); i++; } itemVersionDao.insertChunks(chunks, objectVersion.getId()); } } } private void saveExistentVersion(Item serverObject, ItemMetadata clientMetadata) throws CommitWrongVersion, CommitExistantVersion, DAOException { ItemMetadata serverMetadata = this.getServerObjectVersion(serverObject, clientMetadata.getVersion()); if (!clientMetadata.equals(serverMetadata)) { throw new CommitWrongVersion("Invalid version.", serverObject); } boolean lastVersion = (serverObject.getLatestVersion().equals(clientMetadata.getVersion())); if (!lastVersion) { throw new CommitExistantVersion("This version already exists.", serverObject, clientMetadata.getVersion()); } } private ItemMetadata getCurrentServerVersion(Item serverObject) throws DAOException { return getServerObjectVersion(serverObject, serverObject.getLatestVersion()); } private ItemMetadata getServerObjectVersion(Item serverObject, long requestedVersion) throws DAOException { ItemMetadata metadata = itemVersionDao.findByItemIdAndVersion(serverObject.getId(), requestedVersion); return metadata; } private void beginTransaction() throws DAOException { try { connection.setAutoCommit(false); } catch (SQLException e) { throw new DAOException(e); } } private void commitTransaction() throws DAOException { try { connection.commit(); this.connection.setAutoCommit(true); } catch (SQLException e) { throw new DAOException(e); } } private void rollbackTransaction() throws DAOException { try { this.connection.rollback(); this.connection.setAutoCommit(true); } catch (SQLException e) { throw new DAOException(e); } } }