tv.dyndns.kishibe.qmaclone.server.database.CachedDatabase.java Source code

Java tutorial

Introduction

Here is the source code for tv.dyndns.kishibe.qmaclone.server.database.CachedDatabase.java

Source

//The MIT License
//
//Copyright (c) 2009 nodchip
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package tv.dyndns.kishibe.qmaclone.server.database;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import tv.dyndns.kishibe.qmaclone.client.game.ProblemGenre;
import tv.dyndns.kishibe.qmaclone.client.game.ProblemType;
import tv.dyndns.kishibe.qmaclone.client.game.RandomFlag;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketBbsResponse;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketBbsThread;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketChatMessage;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketLinkData;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketMonth;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketProblem;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketProblemCreationLog;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketProblemMinimum;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketRankingData;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketThemeModeEditLog;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketThemeModeEditor;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketThemeModeEditor.ThemeModeEditorStatus;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketThemeQuery;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketUserData;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketWrongAnswer;
import tv.dyndns.kishibe.qmaclone.client.packet.RestrictionType;
import tv.dyndns.kishibe.qmaclone.server.PageView;
import tv.dyndns.kishibe.qmaclone.server.ThreadPool;
import tv.dyndns.kishibe.qmaclone.server.util.IntArray;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheStats;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.Inject;

public class CachedDatabase implements Database {
    private static final Logger logger = Logger.getLogger(CachedDatabase.class.toString());
    private static final boolean ENABLE_CACHE_STATS = true;
    private static final Object STATIC_KEY = new Object();
    @VisibleForTesting
    final DirectDatabase database;

    // //////////////////////////////////////////////////////////////////////////////
    // 
    @Inject
    public CachedDatabase(DirectDatabase database, ThreadPool threadPool) {
        this.database = Preconditions.checkNotNull(database);

        if (ENABLE_CACHE_STATS) {
            threadPool.addDailyTask(new Runnable() {
                @Override
                public void run() {
                    writeCacheStatsToLog();
                }
            });
            caches.put("themeRankingCache", themeRankingCache);
            caches.put("themeRankingDateRangesCache", themeRankingDateRangesCache);
            caches.put("rankingDataCache", rankingDataCache);
            caches.put("serverIgnoreUserCodeCache", serverIgnoreUserCodeCache);
            caches.put("numberOfActiveUsersCache", numberOfActiveUsersCache);
            caches.put("lastestProblemsCache", lastestProblemsCache);
            caches.put("themeModeEditorsCache", themeModeEditorsCache);
        }
    }

    private void writeCacheStatsToLog() {
        for (Entry<String, Cache<?, ?>> e : caches.entrySet()) {
            String name = e.getKey();
            CacheStats stats = e.getValue().stats();
            double averageLoadPenalty = stats.averageLoadPenalty();
            double hitRate = stats.hitRate();
            String message = String.format("%s hitRate=%.2f averageLoadPenalty=%.2f %s", name, hitRate,
                    averageLoadPenalty, stats.toString());
            logger.log(Level.INFO, message);
        }
    }

    @Override
    public void addChatLog(PacketChatMessage data) throws DatabaseException {
        database.addChatLog(data);
    }

    @Override
    public Map<Integer, PacketChatMessage> getLatestChatData() throws DatabaseException {
        return database.getLatestChatData();
    }

    @Override
    public void addPlayerAnswers(int problemID, ProblemType type, List<String> answers) throws DatabaseException {
        database.addPlayerAnswers(problemID, type, answers);
    }

    @Override
    public void addProblemIdsToReport(int userCode, List<Integer> problemIds) throws DatabaseException {
        database.addProblemIdsToReport(userCode, problemIds);
    }

    @Override
    public void addRatingHistory(int userCode, int rating) throws DatabaseException {
        database.addRatingHistory(userCode, rating);
    }

    @Override
    public void clearProblemFeedback(int problemId) throws DatabaseException {
        database.clearProblemFeedback(problemId);
    }

    @Override
    public void clearProblemIdFromReport(int userCode) throws DatabaseException {
        database.clearProblemIdFromReport(userCode);
    }

    @Override
    public List<PacketWrongAnswer> getPlayerAnswers(int problemID) throws DatabaseException {
        return database.getPlayerAnswers(problemID);
    }

    @Override
    public List<String> getProblemFeedback(int problemId) throws DatabaseException {
        return database.getProblemFeedback(problemId);
    }

    @Override
    public Map<Integer, List<Integer>> getRatingGroupedByPrefecture() throws DatabaseException {
        return database.getRatingGroupedByPrefecture();
    }

    @Override
    public List<Integer> getRatingHistory(int userCode) throws DatabaseException {
        return database.getRatingHistory(userCode);
    }

    @Override
    public List<PacketProblem> getUserProblemReport(int userCode) throws DatabaseException {
        return database.getUserProblemReport(userCode);
    }

    @Override
    public List<Integer> getWholeRating() throws DatabaseException {
        return database.getWholeRating();
    }

    @Override
    public boolean isUsedUserCode(int userCode) throws DatabaseException {
        return database.isUsedUserCode(userCode);
    }

    @Override
    public PageView loadPageView() throws DatabaseException {
        return database.loadPageView();
    }

    @Override
    public void removePlayerAnswers(int problemID) throws DatabaseException {
        database.removePlayerAnswers(problemID);
    }

    @Override
    public void removeProblemIdFromReport(int userCode, int problemID) throws DatabaseException {
        database.removeProblemIdFromReport(userCode, problemID);
    }

    @Override
    public void savePageView(PageView pageView) throws DatabaseException {
        database.savePageView(pageView);
    }

    @Override
    public void updateThemeModeScore(int userCode, String theme, int score) throws DatabaseException {
        database.updateThemeModeScore(userCode, theme, score);
    }

    @Override
    public void voteToProblem(int problemId, boolean good, String feedback) throws DatabaseException {
        database.voteToProblem(problemId, good, feedback);
    }

    @Override
    public void resetVote(int problemId) throws DatabaseException {
        database.resetVote(problemId);
    }

    @Override
    public int getNumberOfCreationLogWithUserCode(int userCode, long dateFrom) throws DatabaseException {
        return database.getNumberOfCreationLogWithUserCode(userCode, dateFrom);
    }

    @Override
    public int getNumberOfCreationLogWithMachineIp(String machineIp, long dateFrom) throws DatabaseException {
        return database.getNumberOfCreationLogWithMachineIp(machineIp, dateFrom);
    }

    @Override
    public int getChatLogId(int year, int month, int day, int hour, int minute, int second)
            throws DatabaseException {
        return database.getChatLogId(year, month, day, hour, minute, second);
    }

    @Override
    public List<PacketChatMessage> getChatLog(int start) throws DatabaseException {
        return database.getChatLog(start);
    }

    @Override
    public int getNumberOfChatLog() throws DatabaseException {
        return database.getNumberOfChatLog();
    }

    @Override
    public List<PacketProblem> getIndicatedProblems() throws DatabaseException {
        return database.getIndicatedProblems();
    }

    private final Map<String, Cache<?, ?>> caches = Maps.newHashMap();

    private <K, V> LoadingCache<K, V> build(String name, CacheLoader<K, V> loader) {
        CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().softValues().expireAfterAccess(1,
                TimeUnit.HOURS);
        if (ENABLE_CACHE_STATS) {
            builder.recordStats();
        }
        LoadingCache<K, V> cache = builder.build(loader);
        caches.put(name, cache);
        return cache;
    }

    // //////////////////////////////////////////////////////////////////////////////
    // 
    private final LoadingCache<Integer, PacketUserData> userDataCache = build("userDataCache",
            new CacheLoader<Integer, PacketUserData>() {
                @Override
                public PacketUserData load(Integer key) throws Exception {
                    return database.getUserData(key);
                }
            });

    public PacketUserData getUserData(int userCode) throws DatabaseException {
        try {
            return userDataCache.get(userCode);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    public void setUserData(PacketUserData data) throws DatabaseException {
        database.setUserData(data);
        userDataCache.invalidate(data.userCode);
    }

    public void removeIgnoreUserCode(int userCode, int targetUserCode) throws DatabaseException {
        database.removeIgnoreUserCode(userCode, targetUserCode);
        userDataCache.invalidate(userCode);
    }

    public void addIgnoreUserCode(int userCode, int targetUserCode) throws DatabaseException {
        database.addIgnoreUserCode(userCode, targetUserCode);
        userDataCache.invalidate(userCode);
    }

    @Override
    public List<PacketUserData> lookupUserCodeByGooglePlusId(String googlePlusId) throws DatabaseException {
        return database.lookupUserCodeByGooglePlusId(googlePlusId);
    }

    @Override
    public void disconnectUserCodeFromGooglePlus(int userCode) throws DatabaseException {
        database.disconnectUserCodeFromGooglePlus(userCode);
    }

    // //////////////////////////////////////////////////////////////////////////////
    // 
    private final LoadingCache<Object, List<List<PacketRankingData>>> rankingDataCache = CacheBuilder.newBuilder()
            .recordStats().expireAfterWrite(1, TimeUnit.HOURS).concurrencyLevel(1)
            .build(new CacheLoader<Object, List<List<PacketRankingData>>>() {
                @Override
                public List<List<PacketRankingData>> load(Object arg0) throws Exception {
                    return database.getGeneralRankingData();
                }
            });

    public List<List<PacketRankingData>> getGeneralRankingData() throws DatabaseException {
        try {
            return rankingDataCache.get(STATIC_KEY);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    // //////////////////////////////////////////////////////////////////////////////
    // ????
    private final LoadingCache<Object, Set<Integer>> serverIgnoreUserCodeCache = CacheBuilder.newBuilder()
            .recordStats().expireAfterWrite(1, TimeUnit.DAYS).concurrencyLevel(1)
            .build(new CacheLoader<Object, Set<Integer>>() {
                @Override
                public Set<Integer> load(Object arg0) throws Exception {
                    return database.getServerIgnoreUserCode();
                }
            });

    public Set<Integer> getServerIgnoreUserCode() throws DatabaseException {
        try {
            return serverIgnoreUserCodeCache.get(STATIC_KEY);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public void addServerIgnoreUserCode(int userCode) throws DatabaseException {
        database.addServerIgnoreUserCode(userCode);
        serverIgnoreUserCodeCache.invalidateAll();
    }

    // ??????
    public List<PacketProblemCreationLog> getProblemCreationHistory(int problemId) throws DatabaseException {
        return database.getProblemCreationHistory(problemId);
    }

    public void addCreationLog(PacketProblem problem, int userCode, String machineIP) throws DatabaseException {
        database.addCreationLog(problem, userCode, machineIP);
    }

    private class BbsResponseCacheKey {
        private final int threadId;
        private final int count;

        private BbsResponseCacheKey(int threadId, int count) {
            this.threadId = threadId;
            this.count = count;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof BbsResponseCacheKey)) {
                return false;
            }
            BbsResponseCacheKey rh = (BbsResponseCacheKey) obj;
            return threadId == rh.threadId && count == rh.count;
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(threadId, count);
        }
    }

    private final LoadingCache<BbsResponseCacheKey, List<PacketBbsResponse>> bbsResponseCache = build(
            "bbsResponseCache", new CacheLoader<BbsResponseCacheKey, List<PacketBbsResponse>>() {
                @Override
                public List<PacketBbsResponse> load(BbsResponseCacheKey key) throws Exception {
                    return database.getBbsResponses(key.threadId, key.count);
                }
            });

    public List<PacketBbsThread> getBbsThreads(int bbsId, int start, int count) throws DatabaseException {
        return database.getBbsThreads(bbsId, start, count);
    }

    public List<PacketBbsResponse> getBbsResponses(int threadId, int count) throws DatabaseException {
        try {
            return bbsResponseCache.get(new BbsResponseCacheKey(threadId, count));
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    public void buildBbsThread(int bbsId, PacketBbsThread thread, PacketBbsResponse response)
            throws DatabaseException {
        database.buildBbsThread(bbsId, thread, response);
        bbsResponseCache.invalidateAll();
    }

    public void writeToBbs(PacketBbsResponse response, boolean age) throws DatabaseException {
        database.writeToBbs(response, age);
        bbsResponseCache.invalidateAll();
    }

    public int getNumberOfBbsThread(int bbsId) throws DatabaseException {
        return database.getNumberOfBbsThread(bbsId);
    }

    // //////////////////////////////////////////////////////////////////////////////
    // 
    private class LinkCacheKey {
        private final int start;
        private final int count;

        private LinkCacheKey(int start, int count) {
            this.start = start;
            this.count = count;
        }

        @Override
        public boolean equals(Object obj) {
            return obj instanceof LinkCacheKey && start == ((LinkCacheKey) obj).start
                    && count == ((LinkCacheKey) obj).count;
        }

        @Override
        public int hashCode() {
            int hash = start;
            hash = 31 * hash + count;
            return hash;
        }
    }

    private final LoadingCache<LinkCacheKey, List<PacketLinkData>> linkCache = build("linkCache",
            new CacheLoader<LinkCacheKey, List<PacketLinkData>>() {
                @Override
                public List<PacketLinkData> load(LinkCacheKey key) throws Exception {
                    return database.getLinkDatas(key.start, key.count);
                }
            });

    private final LoadingCache<Object, Integer> numberOfLinkDataCache = CacheBuilder.newBuilder().recordStats()
            .expireAfterWrite(1, TimeUnit.DAYS).concurrencyLevel(1).build(new CacheLoader<Object, Integer>() {
                @Override
                public Integer load(Object arg0) throws Exception {
                    return database.getNumberOfLinkDatas();
                }
            });

    public void addLinkData(PacketLinkData linkData) throws DatabaseException {
        database.addLinkData(linkData);
        linkCache.invalidateAll();
        numberOfLinkDataCache.invalidateAll();
    }

    public void updateLinkData(PacketLinkData linkData) throws DatabaseException {
        database.updateLinkData(linkData);
        linkCache.invalidateAll();
    }

    public void removeLinkData(int id) throws DatabaseException {
        database.removeLinkData(id);
        linkCache.invalidateAll();
        numberOfLinkDataCache.invalidateAll();
    }

    public List<PacketLinkData> getLinkDatas(int start, int count) throws DatabaseException {
        try {
            return linkCache.get(new LinkCacheKey(start, count));
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    public int getNumberOfLinkDatas() throws DatabaseException {
        try {
            return numberOfLinkDataCache.get(STATIC_KEY);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    // //////////////////////////////////////////////////////////////////////////////
    // 
    private final LoadingCache<Object, Integer> numberOfActiveUsersCache = CacheBuilder.newBuilder().recordStats()
            .expireAfterWrite(1, TimeUnit.HOURS).concurrencyLevel(1).build(new CacheLoader<Object, Integer>() {
                @Override
                public Integer load(Object arg0) throws Exception {
                    return database.getNumberOfActiveUsers();
                }
            });

    public int getNumberOfActiveUsers() throws DatabaseException {
        try {
            return numberOfActiveUsersCache.get(STATIC_KEY);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    // //////////////////////////////////////////////////////////////////////////////
    // 
    @Override
    public List<PacketThemeQuery> getThemeModeQueries() throws DatabaseException {
        return database.getThemeModeQueries();
    }

    @Override
    public List<PacketThemeQuery> getThemeModeQueries(String theme) throws DatabaseException {
        return database.getThemeModeQueries(theme);
    }

    @Override
    public int getNumberOfThemeQueries() throws DatabaseException {
        return database.getNumberOfThemeQueries();
    }

    @Override
    public void addThemeModeQuery(String theme, String query) throws DatabaseException {
        database.addThemeModeQuery(theme, query);
    }

    @Override
    public void removeThemeModeQuery(String theme, String query) throws DatabaseException {
        database.removeThemeModeQuery(theme, query);
    }

    @Override
    public Map<String, IntArray> getThemeToProblems(Map<String, List<String>> themeAndQueryStrings)
            throws DatabaseException {
        return database.getThemeToProblems(themeAndQueryStrings);
    }

    // //////////////////////////////////////////////////////////////////////////////
    // 

    private static class ThemeRankingKey {
        private static final int ALL = Integer.MAX_VALUE;
        private static final int OLD = Integer.MIN_VALUE;
        private final String theme;
        private final int year;
        private final int month;

        public ThemeRankingKey(String theme, int year, int month) {
            this.theme = theme;
            this.year = year;
            this.month = month;
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(theme, year, month);
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof ThemeRankingKey)) {
                return false;
            }
            ThemeRankingKey rh = (ThemeRankingKey) obj;
            return Objects.equal(theme, rh.theme) && year == rh.year && year == rh.month;
        }
    }

    private final LoadingCache<ThemeRankingKey, List<PacketRankingData>> themeRankingCache = CacheBuilder
            .newBuilder().recordStats().expireAfterWrite(1, TimeUnit.HOURS).softValues()
            .build(new CacheLoader<ThemeRankingKey, List<PacketRankingData>>() {
                @Override
                public List<PacketRankingData> load(ThemeRankingKey key) throws Exception {
                    if (key.year == ThemeRankingKey.ALL && key.month == ThemeRankingKey.ALL) {
                        return database.getThemeRankingAll(key.theme);
                    } else if (key.year == ThemeRankingKey.OLD && key.month == ThemeRankingKey.OLD) {
                        return database.getThemeRankingOld(key.theme);
                    } else if (key.month == ThemeRankingKey.ALL) {
                        return database.getThemeRanking(key.theme, key.year);
                    } else {
                        return database.getThemeRanking(key.theme, key.year, key.month);
                    }
                }
            });

    @Override
    public List<PacketRankingData> getThemeRanking(String theme, int year) throws DatabaseException {
        try {
            return themeRankingCache.get(new ThemeRankingKey(theme, year, ThemeRankingKey.ALL));
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public List<PacketRankingData> getThemeRanking(String theme, int year, int month) throws DatabaseException {
        try {
            return themeRankingCache.get(new ThemeRankingKey(theme, year, month));
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public List<PacketRankingData> getThemeRankingAll(String theme) throws DatabaseException {
        try {
            return themeRankingCache.get(new ThemeRankingKey(theme, ThemeRankingKey.ALL, ThemeRankingKey.ALL));
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public List<PacketRankingData> getThemeRankingOld(String theme) throws DatabaseException {
        try {
            return themeRankingCache.get(new ThemeRankingKey(theme, ThemeRankingKey.OLD, ThemeRankingKey.OLD));
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    private final LoadingCache<Object, List<PacketMonth>> themeRankingDateRangesCache = CacheBuilder.newBuilder()
            .concurrencyLevel(1).maximumSize(1).recordStats().refreshAfterWrite(1, TimeUnit.HOURS)
            .build(new CacheLoader<Object, List<PacketMonth>>() {
                @Override
                public List<PacketMonth> load(Object key) throws Exception {
                    return database.getThemeRankingDateRanges();
                }
            });

    @Override
    public List<PacketMonth> getThemeRankingDateRanges() throws DatabaseException {
        try {
            return themeRankingDateRangesCache.get(STATIC_KEY);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    // //////////////////////////////////////////////////////////////////////////////
    // ??
    private final LoadingCache<Object, List<PacketProblem>> lastestProblemsCache = CacheBuilder.newBuilder()
            .recordStats().expireAfterWrite(1, TimeUnit.HOURS).concurrencyLevel(1)
            .build(new CacheLoader<Object, List<PacketProblem>>() {
                @Override
                public List<PacketProblem> load(Object arg0) throws Exception {
                    return database.getLastestProblems();
                }
            });

    @Override
    public List<PacketProblem> getLastestProblems() throws DatabaseException {
        try {
            return lastestProblemsCache.get(STATIC_KEY);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    // //////////////////////////////////////////////////////////////////////////////
    // 
    private final LoadingCache<Object, List<PacketThemeModeEditor>> themeModeEditorsCache = CacheBuilder
            .newBuilder().recordStats().expireAfterWrite(1, TimeUnit.HOURS).concurrencyLevel(1)
            .build(new CacheLoader<Object, List<PacketThemeModeEditor>>() {
                @Override
                public List<PacketThemeModeEditor> load(Object arg0) throws Exception {
                    return database.getThemeModeEditors();
                }
            });

    @Override
    public List<PacketThemeModeEditor> getThemeModeEditors() throws DatabaseException {
        try {
            return themeModeEditorsCache.get(STATIC_KEY);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public ThemeModeEditorStatus getThemeModeEditorsStatus(int userCode) throws DatabaseException {
        return database.getThemeModeEditorsStatus(userCode);
    }

    @Override
    public void updateThemeModeEdtorsStatus(int userCode, ThemeModeEditorStatus status) throws DatabaseException {
        database.updateThemeModeEdtorsStatus(userCode, status);
        themeModeEditorsCache.invalidateAll();
    }

    // //////////////////////////////////////////////////////////////////////////////
    // 
    private final LoadingCache<String, String> passwordCache = build("passwordCache",
            new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    return database.getPassword(key);
                }
            });

    @Override
    public String getPassword(String type) throws DatabaseException {
        try {
            return passwordCache.get(type);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    // //////////////////////////////////////////////////////////////////////////////
    // ?
    private final LoadingCache<Object, List<PacketProblemMinimum>> problemMinimums = CacheBuilder.newBuilder()
            .concurrencyLevel(1).build(new CacheLoader<Object, List<PacketProblemMinimum>>() {
                @Override
                public List<PacketProblemMinimum> load(Object arg0) throws Exception {
                    final List<PacketProblemMinimum> problemMinimums = Lists.newArrayList();
                    problemMinimums.add(new PacketProblemMinimum());
                    // database?processProblems()?????????????
                    database.processProblemMinimums(new ProblemMinimumProcessable() {
                        @Override
                        public void process(PacketProblemMinimum problem) throws Exception {
                            problemMinimums.add(problem);
                        }
                    });

                    Collections.sort(problemMinimums, new Comparator<PacketProblemMinimum>() {
                        @Override
                        public int compare(PacketProblemMinimum o1, PacketProblemMinimum o2) {
                            return o1.id - o2.id;
                        }
                    });

                    return problemMinimums;
                }
            });

    private List<PacketProblemMinimum> getProblemMinimums() throws DatabaseException {
        try {
            return problemMinimums.get(STATIC_KEY);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public int addProblem(PacketProblem data) throws DatabaseException {
        synchronized (problemMinimums) {
            data.id = database.addProblem(data);
            List<PacketProblemMinimum> minimums = getProblemMinimums();
            Preconditions.checkState(data.id <= minimums.size() && minimums.size() <= data.id + 1,
                    "|minimums|=%d data.id=%d", minimums.size(), data.id);
            if (minimums.size() == data.id) {
                minimums.add(data.asMinimum());
            } else {
                minimums.set(data.id, data.asMinimum());
            }
            return data.id;
        }
    }

    @Override
    public List<PacketProblem> getProblem(Collection<Integer> ids) throws DatabaseException {
        return database.getProblem(ids);
    }

    @Override
    public PacketProblemMinimum getProblemMinimum(int problemId) throws DatabaseException {
        synchronized (problemMinimums) {
            return getProblemMinimums().get(problemId);
        }
    }

    @Override
    public void updateMinimumProblem(PacketProblemMinimum data) throws DatabaseException {
        synchronized (problemMinimums) {
            database.updateMinimumProblem(data);
            getProblemMinimums().set(data.id, data);
        }
    }

    @Override
    public void updateProblem(PacketProblem data) throws DatabaseException {
        synchronized (problemMinimums) {
            database.updateProblem(data);
            getProblemMinimums().set(data.id, data);
        }
    }

    @Override
    public void processProblems(ProblemProcessable processor) throws DatabaseException {
        database.processProblems(processor);
    }

    @Override
    public void processProblemMinimums(ProblemMinimumProcessable processer) throws DatabaseException {
        database.processProblemMinimums(processer);
    }

    @Override
    public List<PacketProblem> searchProblem(String query, String creator, boolean creatorPerfectMatching,
            Set<ProblemGenre> genres, Set<ProblemType> types, Set<RandomFlag> randomFlags)
            throws DatabaseException {
        return database.searchProblem(query, creator, creatorPerfectMatching, genres, types, randomFlags);
    }

    @Override
    public List<PacketProblem> searchSimilarProblemFromDatabase(PacketProblem problem) throws DatabaseException {
        return database.searchSimilarProblemFromDatabase(problem);
    }

    @Override
    public List<PacketProblem> getAdsenseProblems(String query) throws DatabaseException {
        return database.getAdsenseProblems(query);
    }

    // ////////////////////////////////////////////////////////////////////////
    // 
    @Override
    public void addThemeModeEditLog(PacketThemeModeEditLog log) throws DatabaseException {
        database.addThemeModeEditLog(log);
    }

    @Override
    public List<PacketThemeModeEditLog> getThemeModeEditLog(int start, int length) throws DatabaseException {
        return database.getThemeModeEditLog(start, length);
    }

    @Override
    public int getNumberOfThemeModeEditLog() throws DatabaseException {
        return database.getNumberOfThemeModeEditLog();
    }

    // //////////////////////////////////////////////////////////////////////////////
    // ?
    // //////////////////////////////////////////////////////////////////////////////
    private final LoadingCache<RestrictionType, Set<Integer>> restrictedUserCodeCache = build("restrictedUserCode",
            new CacheLoader<RestrictionType, Set<Integer>>() {
                @Override
                public Set<Integer> load(RestrictionType key) throws Exception {
                    return database.getRestrictedUserCodes(key);
                }
            });
    private final LoadingCache<RestrictionType, Set<String>> restrictedRemoteAddressCache = build(
            "restrictedRemoteAddress", new CacheLoader<RestrictionType, Set<String>>() {
                @Override
                public Set<String> load(RestrictionType key) throws Exception {
                    return database.getRestrictedRemoteAddresses(key);
                }
            });

    @Override
    public void addRestrictedUserCode(int userCode, RestrictionType restrictionType) throws DatabaseException {
        database.addRestrictedUserCode(userCode, restrictionType);
        restrictedUserCodeCache.invalidate(restrictionType);
    }

    @Override
    public void removeRestrictedUserCode(int userCode, RestrictionType restrictionType) throws DatabaseException {
        database.removeRestrictedUserCode(userCode, restrictionType);
        restrictedUserCodeCache.invalidate(restrictionType);
    }

    @Override
    public Set<Integer> getRestrictedUserCodes(RestrictionType restrictionType) throws DatabaseException {
        try {
            return restrictedUserCodeCache.get(restrictionType);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public void clearRestrictedUserCodes(RestrictionType restrictionType) throws DatabaseException {
        database.clearRestrictedUserCodes(restrictionType);
        restrictedUserCodeCache.invalidateAll();
    }

    @Override
    public void addRestrictedRemoteAddress(String remoteAddress, RestrictionType restrictionType)
            throws DatabaseException {
        database.addRestrictedRemoteAddress(remoteAddress, restrictionType);
        restrictedRemoteAddressCache.invalidate(restrictionType);

    }

    @Override
    public void removeRestrictedRemoteAddress(String remoteAddress, RestrictionType restrictionType)
            throws DatabaseException {
        database.removeRestrictedRemoteAddress(remoteAddress, restrictionType);
        restrictedRemoteAddressCache.invalidate(restrictionType);
    }

    @Override
    public Set<String> getRestrictedRemoteAddresses(RestrictionType restrictionType) throws DatabaseException {
        try {
            return restrictedRemoteAddressCache.get(restrictionType);
        } catch (ExecutionException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public void clearRestrictedRemoteAddresses(RestrictionType restrictionType) throws DatabaseException {
        database.clearRestrictedRemoteAddresses(restrictionType);
        restrictedRemoteAddressCache.invalidate(restrictionType);
    }

    @VisibleForTesting
    void clearCache() {
        userDataCache.invalidateAll();
        rankingDataCache.invalidateAll();
        serverIgnoreUserCodeCache.invalidateAll();
        bbsResponseCache.invalidateAll();
        linkCache.invalidateAll();
        numberOfLinkDataCache.invalidateAll();
        numberOfActiveUsersCache.invalidateAll();
        themeRankingCache.invalidateAll();
        themeRankingDateRangesCache.invalidateAll();
        lastestProblemsCache.invalidateAll();
        themeModeEditorsCache.invalidateAll();
        passwordCache.invalidateAll();
        restrictedUserCodeCache.invalidateAll();
        restrictedRemoteAddressCache.invalidateAll();
        // ?????
        // problemMinimums = null;
    }

    @Override
    public Map<Integer, Integer> getUserCodeToIndicatedProblems() throws DatabaseException {
        return database.getUserCodeToIndicatedProblems();
    }

}