//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();
}
}