nipgm.database.DatabaseService.java Source code

Java tutorial

Introduction

Here is the source code for nipgm.database.DatabaseService.java

Source

/*
 * Copyright (C) 2013 - 2014 Felix Wiemuth
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package nipgm.database;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import nipgm.control.Game;
import nipgm.data.Question;
import nipgm.data.QuestionCategory;
import nipgm.data.db.DBGame;
import nipgm.data.db.DBPlayer;
import nipgm.data.db.DBQuestion;
import nipgm.data.db.DBQuestionCategory;
import nipgm.data.impl.GamePlayer;
import nipgm.data.impl.GameQuestionCategory;
import nipgm.data.persistence.PlayerMapper;
import nipgm.data.persistence.QuestionCategoryMapper;
import nipgm.data.persistence.QuestionMapper;
import nipgm.util.DatabaseUtil;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

/**
 * The database service provides an interface to the database. It allows to
 * obtain objects from and write objects to the database. It throws different
 * exceptions when errors occur:
 * <ul>
 * <li><code>IllegalArgumentException</code>
 * </ul>
 * The excpetion message contains information about the reason of the problem
 * and, where appropriate, which parameters are concerned.
 *
 * @author Felix Wiemuth
 */
public class DatabaseService {

    public static class ObjectNotFoundException extends Exception {

        public ObjectNotFoundException(String message) {
            super(message);
        }
    }

    public static class ItemNotFoundException extends Exception {

        public ItemNotFoundException(String message) {
            super(message);
        }
    }

    private static final String RESOURCE_SQL_INITDB = "/nipgm/resources/sql/initializeDB.sql";
    private static final String RESOURCE_MYBATIS_CONFIG = "nipgm/resources/mybatis-config.xml";
    private final File database;
    private SqlSessionFactory sqlSessionFactory;
    //Important to store categories here: always use the same objects for one category entry of the DB
    private HashMap<Integer, GameQuestionCategory> questionCategoriesMap; //used for
    //    private QuestionCategoryTreeNode categoryTree;
    private GameQuestionCategory rootCategory;

    /**
     * Create a new database service. Note: The database must already be
     * initialized by calling static method <code>initDB()</code>.
     *
     * @param database
     * @param username
     * @param password
     * @throws IOException
     */
    public DatabaseService(File database, String username, String password) throws IOException {
        this.database = database;

        Properties properties = new Properties();
        properties.setProperty("username", username);
        properties.setProperty("password", password);
        properties.setProperty("url", DatabaseUtil.protocol + database.getCanonicalPath());

        InputStream inputStream = Resources.getResourceAsStream(RESOURCE_MYBATIS_CONFIG);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, properties);
        loadCategories();
    }

    public DatabaseService(File database) throws IOException {
        this(database, "", "");
    }

    public GamePlayer loadPlayer(int id) throws ItemNotFoundException {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            PlayerMapper mapper = session.getMapper(PlayerMapper.class);
            DBPlayer dbplayer = mapper.selectPlayer(id);
            if (dbplayer == null) {
                throw new ItemNotFoundException(Game.getFormattedText("ex_playerNotFound", id));
            }
            return new GamePlayer(dbplayer);
        }
    }

    /**
     * Get a list of all player objects in the database.
     *
     * @return
     */
    public List<DBPlayer> getAllPlayers() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            PlayerMapper mapper = session.getMapper(PlayerMapper.class);
            return mapper.selectAllPlayers();
        }
    }

    /**
     * Saves a new player to the database.
     *
     * @param name the name of the player to be created
     * @return
     */
    public GamePlayer createPlayer(String name) {
        DBPlayer dbplayer = new DBPlayer(name);
        try (SqlSession session = sqlSessionFactory.openSession()) {
            PlayerMapper mapper = session.getMapper(PlayerMapper.class);
            mapper.insertPlayer(dbplayer);
            session.commit();
        }
        return new GamePlayer(dbplayer);
    }

    private void loadCategories() {
        Set<DBQuestionCategory> dbQuestionCategories;
        Set<GameQuestionCategory> questionCategories = new HashSet<>();
        Map<GameQuestionCategory, Integer> parentIDs = new HashMap<>();
        questionCategoriesMap = new HashMap<>();
        try (SqlSession session = sqlSessionFactory.openSession()) {
            QuestionCategoryMapper mapper = session.getMapper(QuestionCategoryMapper.class);
            dbQuestionCategories = new HashSet<>(mapper.selectAllCategories()); //TODO directly get as set from myBatis
        }

        Set<GameQuestionCategory> questionCategoriesUnattached = new HashSet<>(); //the nodes where to find a parent for (initialized with all categories but the root category)

        //convert to GameQuestionCategory
        for (DBQuestionCategory dbCategory : dbQuestionCategories) {
            GameQuestionCategory category = new GameQuestionCategory(dbCategory);
            questionCategories.add(category);
            questionCategoriesMap.put(category.getID(), category);
            if (dbCategory.getParentCat() == null) { //found root
                rootCategory = category;
            } else {
                questionCategoriesUnattached.add(category);
            }
            parentIDs.put(category, dbCategory.getParentCat());
        }

        //build tree
        int size;
        do { //while the size of unattached categories shrinks, try to attach
            size = questionCategoriesUnattached.size();
            for (Iterator<GameQuestionCategory> it = questionCategoriesUnattached.iterator(); it.hasNext();) {
                GameQuestionCategory node = it.next();
                Integer nodeParent = parentIDs.get(node);
                for (GameQuestionCategory parent : questionCategories) {
                    if (parent.getID() == nodeParent) {
                        parent.addChildCategory(node);
                        it.remove();
                        break; //continue with next node without parent
                    }
                }
            }
        } while (size > questionCategoriesUnattached.size());

        //now all non-root nodes should be attached
        if (size != 0) {
            throw new IllegalStateException(
                    "The category data in the database is corrupted or there is a bug in the software.");
        }
        if (rootCategory == null) {
            throw new IllegalStateException("Corrupted database: no root category was found.");
        }
    }

    /**
     * Create a new <code>QuestionCategory</code>.
     *
     * @param name
     * @param description
     * @param parentCategory the parent category (required, there is only one
     * default root category)
     * @return
     */
    public QuestionCategory createQuestionCategory(String name, String description,
            QuestionCategory parentCategory) {
        checkNotNull(parentCategory, "parentCategory");
        try (SqlSession session = sqlSessionFactory.openSession()) {
            QuestionCategoryMapper mapper = session.getMapper(QuestionCategoryMapper.class);
            DBQuestionCategory dbcategory = new DBQuestionCategory(name, description, parentCategory.getID());
            mapper.insertCategory(dbcategory);
            session.commit();
            GameQuestionCategory category = new GameQuestionCategory(dbcategory);
            questionCategoriesMap.put(category.getID(), category);
            questionCategoriesMap.get(dbcategory.getParentCat()).addChildCategory(category); //insert into category tree
            return category;
        }
    }

    /**
     * Get the root category. This gives access to the whole category tree which
     * was loaded on construction. All changes to the category tree done over
     * this class' interface are also reflected in the tree obtained.
     *
     * @return
     */
    public QuestionCategory getRootCategory() {
        return rootCategory;
    }

    private List<DBQuestionCategory> getAllCategories() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            QuestionCategoryMapper mapper = session.getMapper(QuestionCategoryMapper.class);
            return mapper.selectAllCategories();
        }
    }

    public Question loadQuestion(int id) throws ObjectNotFoundException {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            QuestionMapper mapper = session.getMapper(QuestionMapper.class);
            DBQuestion dbquestion = mapper.selectQuestion(id);
            if (dbquestion == null) {
                throw new ObjectNotFoundException(Game.getFormattedText("ex_questionNotFound", id));
            }
            return new Question(dbquestion.getQuestion(), questionCategoriesMap.get(dbquestion.getCatID()),
                    dbquestion.getAnswer());
        }
    }

    public List<DBQuestion> getAllDBQuestions() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            QuestionMapper mapper = session.getMapper(QuestionMapper.class);
            List<DBQuestion> questions = mapper.selectAllQuestions();
            return questions;
        }
    }

    public void addQuestion(Question question) {
        DBQuestion dbquestion = new DBQuestion(question.getText(), question.getAnswer().getText(),
                question.getCategory().getID());
        try (SqlSession session = sqlSessionFactory.openSession()) {
            QuestionMapper mapper = session.getMapper(QuestionMapper.class);
            int rows = mapper.insertQuestion(dbquestion);
            session.commit();
        }
    }

    /**
     *
     * @return the auto-generated ID of the game
     */
    public int createDBGame() { //TODO make private
        try (SqlSession session = sqlSessionFactory.openSession()) {
            DBMapper mapper = session.getMapper(DBMapper.class);
            DBGame dbgame = new DBGame();
            mapper.insertGame(dbgame);
            session.commit();
            return dbgame.getId();
        }
    }

    /**
     *
     * @param database
     * @throws nipgm.util.DatabaseUtil.ConnectException
     * @throws FileNotFoundException
     * @throws SQLException
     * @throws IOException
     */
    public static void initDB(File database)
            throws DatabaseUtil.ConnectException, FileNotFoundException, SQLException, IOException {
        try (Connection conn = DatabaseUtil.getConnection(database);
                Reader reader = new BufferedReader(
                        new InputStreamReader(DatabaseService.class.getResourceAsStream(RESOURCE_SQL_INITDB)))) {
            ScriptRunner runner = new ScriptRunner(conn);
            runner.runScript(reader);
        }
        //TODO do without DatabaseService?
        DatabaseService dbservice = new DatabaseService(database);
        dbservice.insertProgramVersion();
        //TODO need to shut down DatabaseService?
    }

    private void insertProgramVersion() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            DBMapper mapper = session.getMapper(DBMapper.class);
            mapper.insertProperty("application_version", Game.version);
            session.commit();
        }
    }

    private void checkNotNull(Object o, String parameterName) throws IllegalArgumentException {
        if (o == null) {
            throw new IllegalArgumentException(String
                    .format("Cannot perform database operation: parameter '%s' must not be null!", parameterName));
        }
    }
}