ch.tatool.app.service.impl.UserAccountServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for ch.tatool.app.service.impl.UserAccountServiceImpl.java

Source

/*******************************************************************************
 * Copyright (c) 2011 Michael Ruflin, Andr Locher, Claudia von Bastian.
 * 
 * This file is part of Tatool.
 * 
 * Tatool is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published 
 * by the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version.
 * 
 * Tatool is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Tatool. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package ch.tatool.app.service.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import ch.tatool.app.Constants;
import ch.tatool.app.data.UserAccountImpl;
import ch.tatool.app.service.UserAccountService;
import ch.tatool.app.util.ContextUtils;
import ch.tatool.data.Messages;
import ch.tatool.data.UserAccount;
import ch.tatool.data.UserAccount.Info;

/**
 * Implementation of the user account service.
 * 
 * Each user account is backed by a database 
 * 
 * @author Michael Ruflin
 *
 */
public class UserAccountServiceImpl implements UserAccountService {

    /** Logger used by this class. */
    Logger logger = LoggerFactory.getLogger(UserAccountServiceImpl.class);

    /** Path where the spring configuration file for db objects resides. */
    private static final String configurationFilePath = "ch/tatool/app/service/impl/account-context.xml";

    /** Name of the project description file. */
    private String descriptionFileName = "account.properties";

    /** Directory where projects should be stored in. */
    private String dataDirName = ".tatooldata";

    /** Should the application data directory be relative to the users home directory?. */
    private boolean relativeToHome = true;

    /** i18n object used to set the locale */
    private Messages messages;

    // UserAccount management

    /**
     * Create a new account folder.
     * 
     * @param userProperties the properties of the user account
     * @param password a password if the user account should be password protected
     */
    public UserAccount createAccount(String accountName, Map<String, String> userProperties, String password) {

        // check if user already exists
        List<UserAccount.Info> accounts = getAccounts();
        for (int i = 0; i < accounts.size(); i++) {
            if (accounts.get(i).getName().equals(accountName)) {
                return null;
            }
        }

        File folder = createAccountFolder();

        // create a new account info object (with empty id)
        UserAccountImpl.InfoImpl info = new UserAccountImpl.InfoImpl();
        info.setFolder(folder);
        info.setName(accountName);
        info.setPasswordProtected(password != null && password.length() > 0);

        // open the database layer with an empty password by default
        UserAccountImpl account = (UserAccountImpl) loadAccount(info, null);

        // write the description file
        info.setId(account.getId());
        writeAccountDescFile(info);

        // set the password of the account if provided
        if (password != null && password.length() > 0) {
            changePassword(account, password);
        }

        // set additional properties of the account
        for (String s : userProperties.keySet()) {
            account.getProperties().put(s, userProperties.get(s));
        }

        saveAccount(account);

        changeLocale(account);

        return account;
    }

    /**
      * Opens an account data object.
      * 
      * The account object is backed by the database. The password is conveniently used
      * as database password, which would fail in case of incorrect password.
      */
    public UserAccount loadAccount(Info info, String password) {
        UserAccountImpl.InfoImpl infoImpl = (UserAccountImpl.InfoImpl) info;

        // load the database support objects (spring does all the wiring work for us
        Properties properties = new Properties();
        String dataPath = new File(infoImpl.getFolder(), "data").getAbsolutePath();
        properties.setProperty("account.data.folder", dataPath);
        if (password != null && password.length() > 0) {
            properties.setProperty("account.password", password);
        } else {
            properties.setProperty("account.password", "");
        }

        BeanFactory beanFactory = ContextUtils.createBeanFactory(configurationFilePath, properties);

        // create an account object, and bind it to the database
        final UserAccountImpl userAccount = new UserAccountImpl();
        userAccount.setFolder(infoImpl.getFolder());
        userAccount.setBeanFactory(beanFactory);
        userAccount.setPassword(password);
        userAccount.setName(infoImpl.getName());
        userAccount.setId(infoImpl.getId());

        // initialize transaction management
        PlatformTransactionManager transactionManager = (PlatformTransactionManager) beanFactory
                .getBean("userAccountTxManager");
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        userAccount.setTransactionTemplate(transactionTemplate);

        // load the account in a transaction
        try {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                public void doInTransactionWithoutResult(TransactionStatus status) {
                    UserAccountDAO userAccountDAO = userAccount.getUserAccountDAO();
                    userAccountDAO.loadAccount(userAccount);
                }
            });

        } catch (org.hibernate.ObjectNotFoundException onfe) {
            logger.warn("Databasee entry for account does not exist anymore. Got the database deleted?");
            throw new RuntimeException("Unable to load user account due to missing data");
        }

        changeLocale(userAccount);

        return userAccount;
    }

    /**
     * Changes the currently used Locale which is used to determine the language of Tatool.
     */
    private void changeLocale(UserAccountImpl account) {
        String lang = account.getProperties().get(Constants.PROPERTY_ACCOUNT_LANG);
        if (lang == null) {
            lang = "en";
        }
        Locale local = new Locale(lang);
        messages.setLocale(local);
    }

    /**
     * Saves an account.
     */
    public void saveAccount(UserAccount account) {
        // write the database
        final UserAccountImpl userAccount = (UserAccountImpl) account;
        userAccount.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                UserAccountDAO userAccountDAO = userAccount.getUserAccountDAO();
                userAccountDAO.saveAccount(userAccount);
            }
        });
        updateUserAccountInfo(userAccount);
    }

    /**
     * Closes an opened account.
     * This method should free all opened resources.
     * 
     * Note: This method does NOT save the account data!
     */
    public void closeAccount(UserAccount account) {
        // force the database to shutdown
        final UserAccountImpl userAccount = (UserAccountImpl) account;
        // shutdown the database - necessary for hsql. we do it currently by adding a shutdown=true parameter to the connection string
        userAccount.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                UserAccountDAO userAccountDAO = userAccount.getUserAccountDAO();
                userAccountDAO.shutdown();
            }
        });

        // destroy all singletons - this will trigger the close method of the DataSource 
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) userAccount.getBeanFactory();
        beanFactory.destroySingletons();
    }

    /**
     * Change the password for an account.
     */
    public void changePassword(UserAccount account, final String newPassword) {
        final UserAccountImpl userAccount = (UserAccountImpl) account;
        userAccount.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                UserAccountDAO userAccountDAO = userAccount.getUserAccountDAO();
                userAccountDAO.setAccountPassword(userAccount, newPassword);
            }
        });
        updateUserAccountInfo(userAccount);
    }

    /** Delete a module.
     * 
     * This method should completely remove all module related data from the system.
     */
    public void deleteAccount(UserAccount account) {
        final UserAccountImpl userAccount = (UserAccountImpl) account;

        // delete the data object
        userAccount.getTransactionTemplate().execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                UserAccountDAO userAccountDAO = userAccount.getUserAccountDAO();
                userAccountDAO.deleteAccount(userAccount);
            }
        });

        // first close the account
        closeAccount(account);

        // then delete the account folder
        File folder = userAccount.getFolder();
        if (folder.exists()) {
            // delete the folder and all files in it
            try {
                logger.info("Deleting directory {}", folder.getAbsolutePath());
                FileUtils.deleteDirectory(folder);
            } catch (IOException ioe) {
                logger.error("Unable to delete module directory!", ioe);
            }
        }
    }

    //
    // Account folder management and info files
    //

    /**
     * Get all available module instances.
     */
    public List<UserAccount.Info> getAccounts() {
        // each module is stored in a separate folder inside the data folder
        File dataFolder = getTatoolDataFolder();

        // each module has a properties file that describes the module.
        // read each of them and construct the corresponding module objects.
        List<UserAccount.Info> accounts = new ArrayList<UserAccount.Info>();
        File[] children = dataFolder.listFiles();
        for (File f : children) {
            if (f.isDirectory()) {
                UserAccount.Info info = getAccountInfos(f);
                if (info != null) {
                    accounts.add(info);
                } else {
                    logger.info("Non-project folder: {}", f.getAbsolutePath());
                }
            }
        }

        return accounts;
    }

    /**
     * Get a account info file for an account folder
     */
    private Info getAccountInfos(File folder) {
        // only proceed if a description file exists
        File descriptionFile = getAccountDescFile(folder);
        if (descriptionFile == null) {
            logger.info("No project description file exists for folder {}", folder);
            return null;
        }

        // read the description file
        Properties p = readAccountDescFile(descriptionFile);
        if (p == null) {
            logger.info("Unable to read description file {}", descriptionFile.getAbsolutePath());
            return null;
        }

        // create and return a ModuleInfo object
        UserAccountImpl.InfoImpl info = new UserAccountImpl.InfoImpl();
        info.setFolder(folder);
        info.setName(p.getProperty("name", "Unknown account name"));
        info.setPasswordProtected(Boolean.parseBoolean(p.getProperty("passwordProtected", "false")));
        String idString = p.getProperty("id");
        if (idString != null) {
            try {
                info.setId(Long.parseLong(idString));
            } catch (NumberFormatException nfe) {
                logger.warn("Unable to read account id", nfe);
            }
        }
        return info;
    }

    private void updateUserAccountInfo(UserAccountImpl userAccount) {
        // update the description file
        UserAccountImpl.InfoImpl info = new UserAccountImpl.InfoImpl();
        info.setName(userAccount.getName());
        info.setPasswordProtected(userAccount.isPasswordProtected());
        info.setFolder(userAccount.getFolder());
        info.setId(userAccount.getId());
        writeAccountDescFile(info);
    }

    /**
     * Reads the account description file
     * 
     * @param descriptionFileName
     * @return a properties object or null in case an error occured
     */
    private Properties readAccountDescFile(File descFile) {
        try {
            FileInputStream fis = new FileInputStream(descFile);
            Properties p = new Properties();
            p.load(fis);
            fis.close();
            return p;
        } catch (IOException ioe) {
            logger.error("Unable to read module description file", ioe);
            return null;
        }
    }

    /** Writes the description to a properties file. */
    private void writeAccountDescFile(UserAccountImpl.InfoImpl info) {
        // make sure the file exists - create if it doesn't
        File descriptionFile = createAccountDescFile(info);

        Properties p = new Properties();
        p.setProperty("name", info.getName());
        p.setProperty("passwordProtected", String.valueOf(info.isPasswordProtected()));
        p.setProperty("id", info.getId().toString());
        try {
            FileOutputStream fos = new FileOutputStream(descriptionFile);
            p.store(fos, "Tatool account info");
            fos.close();
        } catch (IOException ioe) {
            logger.error("Unable to write account info file", ioe);
        }
    }

    /** Creates a module description file. */
    private File createAccountDescFile(UserAccountImpl.InfoImpl info) {
        File folder = info.getFolder();

        // create the file
        File descriptionFile = new File(folder, descriptionFileName);
        if (descriptionFile.exists()) {
            return descriptionFile;
        }

        logger.info("Creating description file {}", descriptionFile.getAbsolutePath());
        try {
            descriptionFile.createNewFile();
            return descriptionFile;
        } catch (IOException ioe) {
            logger.error("Unable to create description file", ioe);
            return null;
        }
    }

    /**
     * Get the description file
     * 
     * @param moduleFolder the module folder
     * @return the description file or null if it does not exist
     */
    private File getAccountDescFile(File folder) {
        File descFile = new File(folder, descriptionFileName);
        if (descFile.exists()) {
            return descFile;
        } else {
            return null;
        }
    }

    /**
     * Create a new module folder given an id.
     * 
     * @param id the id to use for the new folder
     * @return the folder or null if a module with given id already exists.
     */
    private File createAccountFolder() {
        // let's take the date as folder name - is saver than anything else
        File tatoolDataFolder = getTatoolDataFolder();
        File moduleFolder = null;
        do {
            moduleFolder = new File(tatoolDataFolder, String.valueOf(System.currentTimeMillis()));
        } while (moduleFolder.exists());

        // create the folder and return it if we succeed
        boolean created = moduleFolder.mkdirs();
        return created ? moduleFolder : null;
    }

    /**
     * Get the Tatool data folder.
     * 
     * Note: The folder is created if it does not yet exist.
     * 
     * @return the folder
     */
    public File getTatoolDataFolder() {
        File userHome = null;
        if (relativeToHome) {
            userHome = new File(System.getProperty("user.home"));
        } else {
            userHome = new File(".");
        }

        // get or create the data folder
        File dataFolder = new File(userHome, dataDirName);
        if (!dataFolder.exists()) {
            boolean created = dataFolder.mkdirs();
            if (!created) {
                logger.error("Cannot create data directory: {}", dataFolder.getAbsolutePath());
                throw new RuntimeException("Unable to create data directory: " + dataFolder.getAbsolutePath());
            }
        }

        return dataFolder;
    }

    public String getDataDirName() {
        return dataDirName;
    }

    public void setDataDirName(String dataDirName) {
        this.dataDirName = dataDirName;
    }

    public boolean isRelativeToHome() {
        return relativeToHome;
    }

    public void setRelativeToHome(boolean relativeToHome) {
        this.relativeToHome = relativeToHome;
    }

    public void setMessages(Messages messages) {
        this.messages = messages;
    }

    public Messages getMessages() {
        return messages;
    }
}