org.languagetool.server.DatabaseAccess.java Source code

Java tutorial

Introduction

Here is the source code for org.languagetool.server.DatabaseAccess.java

Source

/* LanguageTool, a natural language style checker
 * Copyright (C) 2018 Daniel Naber (http://www.danielnaber.de)
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */
package org.languagetool.server;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.languagetool.Language;

import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static org.languagetool.server.ServerTools.print;

/**
 * Encapsulate database access. Will do nothing if database access is not configured.
 * @since 4.2
 */
class DatabaseAccess {

    private static DatabaseAccess instance;
    private static SqlSessionFactory sqlSessionFactory;

    private final Cache<Long, List<UserDictEntry>> userDictCache = CacheBuilder.newBuilder().maximumSize(1000)
            .expireAfterWrite(24, TimeUnit.HOURS).build();

    private DatabaseAccess(HTTPServerConfig config) {
        if (config.getDatabaseDriver() != null) {
            try {
                print("Setting up database access, URL " + config.getDatabaseUrl() + ", driver: "
                        + config.getDatabaseDriver() + ", user: " + config.getDatabaseUsername());
                InputStream inputStream = Resources
                        .getResourceAsStream("org/languagetool/server/mybatis-config.xml");
                Properties properties = new Properties();
                properties.setProperty("driver", config.getDatabaseDriver());
                properties.setProperty("url", config.getDatabaseUrl());
                properties.setProperty("username", config.getDatabaseUsername());
                properties.setProperty("password", config.getDatabasePassword());
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, properties);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            print("Not setting up database access, dbDriver is not configured");
        }
    }

    static synchronized void init(HTTPServerConfig config) {
        if (instance == null) {
            instance = new DatabaseAccess(config);
        }
    }

    static synchronized DatabaseAccess getInstance() {
        if (instance == null) {
            throw new IllegalStateException("DatabaseAccess.init() has not been called yet");
        }
        return instance;
    }

    List<String> getUserDictWords(Long userId) {
        List<String> dictEntries = new ArrayList<>();
        if (sqlSessionFactory == null) {
            return dictEntries;
        }
        try (SqlSession session = sqlSessionFactory.openSession()) {
            try {
                List<UserDictEntry> dict = session
                        .selectList("org.languagetool.server.UserDictMapper.selectWordList", userId);
                for (UserDictEntry userDictEntry : dict) {
                    dictEntries.add(userDictEntry.getWord());
                }
                if (dict.size() <= 1000) { // make sure users with huge dict don't blow up the cache
                    userDictCache.put(userId, dict);
                } else {
                    print("WARN: Large dict size " + dict.size() + " for user " + userId
                            + " - will not put user's dict in cache");
                }
            } catch (Exception e) {
                // try to be more robust when database is down, i.e. don't just crash but try to use cache:
                List<UserDictEntry> cachedDictOrNull = userDictCache.getIfPresent(userId);
                if (cachedDictOrNull != null) {
                    print("ERROR: Could not get words from database for user " + userId + ": " + e.getMessage()
                            + ", will use cached version (" + cachedDictOrNull.size()
                            + " items). Full stack trace follows:", System.err);
                    for (UserDictEntry userDictEntry : cachedDictOrNull) {
                        dictEntries.add(userDictEntry.getWord());
                    }
                } else {
                    print("ERROR: Could not get words from database for user " + userId + ": " + e.getMessage()
                            + " - also, could not use version from cache, user id not found in cache, will use empty dict. Full stack trace follows:",
                            System.err);
                }
                e.printStackTrace();
            }
        }
        return dictEntries;
    }

    List<UserDictEntry> getWords(Long userId, int offset, int limit) {
        if (sqlSessionFactory == null) {
            return new ArrayList<>();
        }
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            Map<Object, Object> map = new HashMap<>();
            map.put("userId", userId);
            return session.selectList("org.languagetool.server.UserDictMapper.selectWordList", map,
                    new RowBounds(offset, limit));
        }
    }

    boolean addWord(String word, Long userId) {
        validateWord(word);
        if (sqlSessionFactory == null) {
            return false;
        }
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            Map<Object, Object> map = new HashMap<>();
            map.put("word", word);
            map.put("userId", userId);
            List<UserDictEntry> existingWords = session
                    .selectList("org.languagetool.server.UserDictMapper.selectWord", map);
            if (existingWords.size() >= 1) {
                print("Did not add '" + word + "' for user " + userId
                        + " to list of ignored words, already exists");
                return false;
            } else {
                Date now = new Date();
                map.put("created_at", now);
                map.put("updated_at", now);
                int affectedRows = session.insert("org.languagetool.server.UserDictMapper.addWord", map);
                print("Added '" + word + "' for user " + userId + " to list of ignored words, affectedRows: "
                        + affectedRows);
                return affectedRows == 1;
            }
        }
    }

    Long getUserId(String username, String apiKey) {
        if (username == null || username.trim().isEmpty()) {
            throw new IllegalArgumentException("username must be set");
        }
        if (apiKey == null || apiKey.trim().isEmpty()) {
            throw new IllegalArgumentException("apikey must be set");
        }
        if (sqlSessionFactory == null) {
            throw new IllegalStateException(
                    "sqlSessionFactory not initialized - has the database been configured?");
        }
        try (SqlSession session = sqlSessionFactory.openSession()) {
            Map<Object, Object> map = new HashMap<>();
            map.put("username", username);
            map.put("apiKey", apiKey);
            Long id = session.selectOne("org.languagetool.server.UserDictMapper.getUserIdByApiKey", map);
            if (id == null) {
                throw new IllegalArgumentException(
                        "No user found for given username '" + username + "' and given api key");
            }
            return id;
        }
    }

    boolean deleteWord(String word, Long userId) {
        if (sqlSessionFactory == null) {
            return false;
        }
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            HashMap<Object, Object> map = new HashMap<>();
            map.put("word", word);
            map.put("userId", userId);
            int count = session.delete("org.languagetool.server.UserDictMapper.selectWord", map);
            if (count == 0) {
                print("Did not delete '" + word + "' for user " + userId
                        + " from list of ignored words, does not exist");
                return false;
            } else {
                int affectedRows = session.delete("org.languagetool.server.UserDictMapper.deleteWord", map);
                print("Deleted '" + word + "' for user " + userId + " from list of ignored words, affectedRows: "
                        + affectedRows);
                return affectedRows >= 1;
            }
        }
    }

    void logAccess(Language lang, int textSize, int matches, Long userId) {
        if (sqlSessionFactory == null) {
            return;
        }
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            HashMap<Object, Object> map = new HashMap<>();
            Calendar date = Calendar.getInstance();
            SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            map.put("day", dayFormat.format(date.getTime()));
            map.put("date", dateFormat.format(date.getTime()));
            map.put("user_id", userId);
            map.put("textsize", textSize);
            map.put("matches", matches);
            map.put("language", lang.getShortCodeWithCountryAndVariant());
            session.insert("org.languagetool.server.LogMapper.logCheck", map);
        } catch (Exception e) {
            print("Could not log check for " + userId + ": " + e.getMessage());
        }
    }

    private void validateWord(String word) {
        if (word == null || word.trim().isEmpty()) {
            throw new IllegalArgumentException("Invalid word, cannot be empty or whitespace only");
        }
        if (word.matches(".*\\s.*")) {
            throw new IllegalArgumentException(
                    "Invalid word, you can only words that don't contain spaces: '" + word + "'");
        }
    }

    /** For unit tests only! */
    public static void createAndFillTestTables() {
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            System.out.println("Setting up tables and adding test user...");
            session.insert("org.languagetool.server.UserDictMapper.createUserTable");
            session.insert("org.languagetool.server.UserDictMapper.createIgnoreWordTable");
            session.insert("org.languagetool.server.UserDictMapper.createTestUser1");
            session.insert("org.languagetool.server.UserDictMapper.createTestUser2");
        }
    }

    /** For unit tests only! */
    public static void deleteTestTables() {
        try (SqlSession session = sqlSessionFactory.openSession(true)) {
            System.out.println("Deleting tables...");
            session.delete("org.languagetool.server.UserDictMapper.deleteUsersTable");
            session.delete("org.languagetool.server.UserDictMapper.deleteIgnoreWordsTable");
        }
    }
}