//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;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.google.gwt.thirdparty.guava.common.base.Strings;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.name.Named;
import tv.dyndns.kishibe.qmaclone.client.Service;
import tv.dyndns.kishibe.qmaclone.client.constant.Constant;
import tv.dyndns.kishibe.qmaclone.client.game.GameMode;
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.NewAndOldProblems;
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.PacketChatMessages;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketGameStatus;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketImageLink;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketLogin;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketMatchingStatus;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketMonth;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketPlayerSummary;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketProblem;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketProblemCreationLog;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketRankingData;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketRatingDistribution;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketReadyForGame;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketRegistrationData;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketResult;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketRoomKey;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketServerStatus;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketTheme;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketThemeModeEditLog;
import tv.dyndns.kishibe.qmaclone.client.packet.PacketThemeModeEditor;
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.ProblemIndicationEligibility;
import tv.dyndns.kishibe.qmaclone.client.packet.RestrictionType;
import tv.dyndns.kishibe.qmaclone.client.service.ServiceException;
import tv.dyndns.kishibe.qmaclone.server.database.Database;
import tv.dyndns.kishibe.qmaclone.server.database.DatabaseException;
import tv.dyndns.kishibe.qmaclone.server.exception.GameNotFoundException;
import tv.dyndns.kishibe.qmaclone.server.handwriting.Recognizable;
import tv.dyndns.kishibe.qmaclone.server.image.BrokenImageLinkDetector;
import tv.dyndns.kishibe.qmaclone.server.service.DatabaseAccessible;
import tv.dyndns.kishibe.qmaclone.server.sns.SnsClient;
import tv.dyndns.kishibe.qmaclone.server.sns.SnsClients;
import tv.dyndns.kishibe.qmaclone.server.util.IntArray;
import tv.dyndns.kishibe.qmaclone.server.util.diff_match_patch;
import tv.dyndns.kishibe.qmaclone.server.util.diff_match_patch.Diff;
@SuppressWarnings("serial")
public class ServiceServletStub extends RemoteServiceServlet implements Service {
private static final Logger logger = Logger.getLogger(ServiceServletStub.class.toString());
private static final File PROBLEM_CREATION_LOG_FILE = new File("/tmp/qmaclone/problem.log");
private static final Set<String> LOGGING_EXCLUDED_METHODS = ImmutableSet.of("keepAlive",
"getServerStatus", "getGameStatus", "keepAliveGame", "waitForGame", "receiveMessageFromChat");
private final Random random = new Random();
private final ChatManager chatManager;
private final NormalModeProblemManager normalModeProblemManager;
private final ThemeModeProblemManager themeModeProblemManager;;
private final GameManager gameManager;
private final ServerStatusManager serverStatusManager;
private final PlayerHistoryManager playerHistoryManager;
private final VoteManager voteManager;
private final Recognizable recognizer;
private final ThemeModeEditorManager themeModeEditorManager;
private final Server server;
private final Database database;
private final PrefectureRanking prefectureRanking;
private final RatingDistribution ratingDistribution;
private final SnsClient snsClient;
private final GameLogger gameLogger;
private final ThreadPool threadPool;
private final RestrictedUserUtils restrictedUserUtils;
private final ProblemCorrectCounterResetCounter problemCorrectCounterResetCounter;
private final ProblemIndicationCounter problemIndicationCounter;
private final BrokenImageLinkDetector brokenImageLinkDetector;
/**
* Only for testing.
*/
public ServiceServletStub() {
Injector injector = Guice.createInjector(new QMACloneModule());
this.chatManager = injector.getInstance(ChatManager.class);
this.normalModeProblemManager = injector.getInstance(NormalModeProblemManager.class);
this.themeModeProblemManager = injector.getInstance(ThemeModeProblemManager.class);
this.gameManager = injector.getInstance(GameManager.class);
this.serverStatusManager = injector.getInstance(ServerStatusManager.class);
this.playerHistoryManager = injector.getInstance(PlayerHistoryManager.class);
this.voteManager = injector.getInstance(VoteManager.class);
this.recognizer = injector.getInstance(Recognizable.class);
this.themeModeEditorManager = injector.getInstance(ThemeModeEditorManager.class);
this.server = injector.getInstance(Server.class);
this.database = injector.getInstance(Database.class);
this.prefectureRanking = injector.getInstance(PrefectureRanking.class);
this.ratingDistribution = injector.getInstance(RatingDistribution.class);
this.snsClient = injector.getInstance(SnsClients.class);
this.gameLogger = injector.getInstance(GameLogger.class);
this.threadPool = injector.getInstance(ThreadPool.class);
this.restrictedUserUtils = injector.getInstance(RestrictedUserUtils.class);
this.problemCorrectCounterResetCounter = injector
.getInstance(ProblemCorrectCounterResetCounter.class);
this.problemIndicationCounter = injector.getInstance(ProblemIndicationCounter.class);
this.brokenImageLinkDetector = injector.getInstance(BrokenImageLinkDetector.class);
}
@Inject
public ServiceServletStub(ChatManager chatManager,
NormalModeProblemManager normalModeProblemManager,
ThemeModeProblemManager themeModeProblemManager, GameManager gameManager,
ServerStatusManager serverStatusManager, PlayerHistoryManager playerHistoryManager,
VoteManager voteManager, Recognizable recognizer,
ThemeModeEditorManager themeModeEditorManager, Server server, Database database,
PrefectureRanking prefectureRanking, RatingDistribution ratingDistribution,
@Named("SnsClients") SnsClient snsClient, GameLogger gameLogger, ThreadPool threadPool,
BadUserDetector badUserDetector, RestrictedUserUtils restrictedUserUtils,
ProblemCorrectCounterResetCounter problemCorrectCounterResetCounter,
ProblemIndicationCounter problemIndicationCounter,
BrokenImageLinkDetector brokenImageLinkDetector) throws SocketException {
this.chatManager = chatManager;
this.normalModeProblemManager = normalModeProblemManager;
this.themeModeProblemManager = themeModeProblemManager;
this.gameManager = gameManager;
this.serverStatusManager = serverStatusManager;
this.playerHistoryManager = playerHistoryManager;
this.voteManager = voteManager;
this.recognizer = recognizer;
this.themeModeEditorManager = themeModeEditorManager;
this.server = server;
this.database = database;
this.prefectureRanking = prefectureRanking;
this.ratingDistribution = ratingDistribution;
this.snsClient = snsClient;
this.gameLogger = gameLogger;
this.threadPool = threadPool;
this.restrictedUserUtils = Preconditions.checkNotNull(restrictedUserUtils);
this.problemCorrectCounterResetCounter = Preconditions
.checkNotNull(problemCorrectCounterResetCounter);
this.problemIndicationCounter = Preconditions.checkNotNull(problemIndicationCounter);
this.brokenImageLinkDetector = Preconditions.checkNotNull(brokenImageLinkDetector);
// テーマモードのTwitter通知タイミング調整
threadPool.scheduleWithFixedDelay(commandUpdateThemeModeNotificationCounter, 1, 1,
TimeUnit.SECONDS);
threadPool.addHourTask(badUserDetector);
threadPool.addHourTask(problemCorrectCounterResetCounter);
threadPool.addHourTask(problemIndicationCounter);
threadPool.addDailyTask(brokenImageLinkDetector);
if (!onDevelopmentMachine()) {
threadPool.execute(brokenImageLinkDetector);
}
}
private static boolean onDevelopmentMachine() throws SocketException {
for (NetworkInterface networkInterface : Collections
.list(NetworkInterface.getNetworkInterfaces())) {
for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) {
if (inetAddress.getHostAddress().contains("192.168.100.5")) {
return true;
}
}
}
return false;
}
@Override
protected boolean shouldCompressResponse(HttpServletRequest request, HttpServletResponse response,
String responsePayload) {
// nginx側で圧縮するためtomcat側で圧縮しないようにする
return false;
}
@Override
public String processCall(String payload) {
// // ロギング
// RPCRequest rpcRequest = RPC.decodeRequest(payload, getClass(), this);
// if (!LOGGING_EXCLUDED_METHODS.contains(rpcRequest.getMethod().getName())) {
// logger.info(getRemoteAddress() + " " + rpcRequest.getMethod());
// }
// processCall()から送出される例外を捕捉してログに出力する
try {
return super.processCall(payload);
} catch (Throwable e) {
String message = "processCall()中にエラーが発生しました: "
+ MoreObjects.toStringHelper(this).add("remoteAddress", getRemoteAddress())
.add("rpcRequest", RPC.decodeRequest(payload, null, this)).add("payload", payload)
.toString();
logger.log(Level.WARNING, message, e);
try {
return RPC.encodeResponseForFailure(null,
new ServiceException(Throwables.getStackTraceAsString(e)));
} catch (SerializationException e1) {
logger.log(Level.WARNING, "エラー情報の返信に失敗しました", e1);
}
}
// フォールバック
return "";
}
@Override
public void destroy() {
try {
server.stop();
} catch (Exception e) {
logger.log(Level.WARNING, "WebSocketの停止に失敗しました", e);
}
threadPool.shutdown();
super.destroy();
}
@Override
public PacketServerStatus getServerStatus() {
return serverStatusManager.getServerStatus();
}
@Override
public PacketLogin login(int userCode) {
serverStatusManager.login();
serverStatusManager.keepAlive(userCode);
PacketLogin login = new PacketLogin();
// login.removeAddress = getRemoteAddress();
return login;
}
@Override
public void keepAlive(int userCode) {
serverStatusManager.keepAlive(userCode);
}
// プレイヤー情報を登録する
private Object lockObjectRegister = new Object();
@Override
public PacketRegistrationData register(PacketPlayerSummary playerSummary,
Set<ProblemGenre> genres, Set<ProblemType> types, String greeting, GameMode gameMode,
String roomName, String theme, String imageFileName, int classLevel, int difficultSelect,
int rating, int userCode, int volatility, int playCount, NewAndOldProblems newAndOldProblems,
boolean publicEvent) throws ServiceException {
synchronized (lockObjectRegister) {
try {
gameLogger.write(MoreObjects.toStringHelper(this).add("method", "register")
.add("playerSummary", playerSummary).add("genres", genres).add("types", types)
.add("greeting", greeting).add("gameMode", gameMode).add("roomName", roomName)
.add("THEME", theme).add("imageFileName", imageFileName).add("classLevel", classLevel)
.add("difficultSelect", difficultSelect).add("rating", rating).add("userCode", userCode)
.add("volatility", volatility).add("playCount", playCount)
.add("newAndOldProblems", newAndOldProblems).add("publicEvent", publicEvent)
.toString());
playerHistoryManager.push(playerSummary);
PlayerStatus status;
Game session = gameManager.getOrCreateMatchingSession(gameMode, roomName, classLevel, theme,
genres, types, publicEvent, serverStatusManager, userCode, getRemoteAddress());
status = session.addPlayer(playerSummary, genres, types, greeting, imageFileName,
classLevel, difficultSelect, rating, userCode, volatility, playCount,
newAndOldProblems);
PacketRegistrationData data = new PacketRegistrationData();
data.playerListIndex = status.getPlayerListId();
data.sessionId = status.getSessionId();
return data;
} catch (Exception e) {
String parameters = MoreObjects.toStringHelper(this).add("playerSummary", playerSummary)
.add("genres", genres).add("types", types).add("greeting", greeting)
.add("gameMode", gameMode).add("roomName", roomName).add("THEME", theme)
.add("imageFileName", imageFileName).add("classLevel", classLevel)
.add("difficultSelect", difficultSelect).add("rating", rating).add("userCode", userCode)
.add("volatility", volatility).add("playCount", playCount)
.add("newAndOldProblems", newAndOldProblems).add("publicEvent", publicEvent).toString();
logger.log(Level.SEVERE, "プレイヤー登録に失敗しました。" + parameters, e);
throw new ServiceException(e);
}
}
}
// マッチング
@Override
public PacketMatchingStatus getMatchingStatus(int sessionId) throws ServiceException {
Game session;
try {
session = gameManager.getSession(sessionId);
} catch (GameNotFoundException e) {
String message = "ゲームセッションが見つかりませんでした: sessionId=" + sessionId;
logger.log(Level.WARNING, message, e);
throw new ServiceException(message, e);
}
return session.getMatchingStatus();
}
// 強制的にゲームをスタートさせる
@Override
public int requestSkip(int sessionId, int playerListId) throws ServiceException {
Game session;
try {
session = gameManager.getSession(sessionId);
} catch (GameNotFoundException e) {
String message = "ゲームセッションが見つかりませんでした: sessionId=" + sessionId;
logger.log(Level.WARNING, message, e);
throw new ServiceException(message, e);
}
session.requestStartingGame(playerListId);
return session.getNumberOfPlayer();
}
// ゲーム開始待機
// ゲームの開始を待つ
@Override
public PacketReadyForGame waitForGame(int sessionId) throws ServiceException {
Game session;
try {
session = gameManager.getSession(sessionId);
} catch (GameNotFoundException e) {
String message = "ゲームセッションが見つかりませんでした: sessionId=" + sessionId;
logger.log(Level.WARNING, message, e);
throw new ServiceException(message, e);
}
return session.getReadyForGameStatus();
}
// 問題を取得する
@Override
public List<PacketProblem> getProblem(int sessionId) throws ServiceException {
Game session;
try {
session = gameManager.getSession(sessionId);
} catch (GameNotFoundException e) {
String message = "ゲームセッションが見つかりませんでした: sessionId=" + sessionId;
logger.log(Level.WARNING, message, e);
throw new ServiceException(message, e);
}
return session.getProblem();
}
// 他のプレイヤーの名前を取得する
@Override
public List<PacketPlayerSummary> getPlayerSummaries(int sessionId) throws ServiceException {
Game session;
try {
session = gameManager.getSession(sessionId);
} catch (GameNotFoundException e) {
String message = "ゲームセッションが見つかりませんでした: sessionId=" + sessionId;
logger.log(Level.WARNING, message, e);
throw new ServiceException(message, e);
}
return session.getPlayerSummaries();
}
@Override
public void sendAnswer(int sessionId, int playerListId, String answer, int userCode,
int responseTime) throws ServiceException {
Game session;
try {
session = gameManager.getSession(sessionId);
} catch (GameNotFoundException e) {
String message = "ゲームセッションが見つかりませんでした: sessionId=" + sessionId;
logger.log(Level.WARNING, message, e);
throw new ServiceException(message, e);
}
session.receiveAnswer(playerListId, answer);
gameLogger.write(MoreObjects.toStringHelper(this).add("method", "sendAnswer")
.add("sessionId", sessionId).add("playerListId", playerListId)
.add("answer",
Arrays.deepToString(Strings.nullToEmpty(answer).split(Constant.DELIMITER_GENERAL)))
.add("userCode", userCode).add("responseTime", responseTime)
.add("remoteAddress", getRemoteAddress()).toString());
}
@Override
public void notifyTimeUp(int sessionId, int playerListId, int userCode) {
gameLogger.write(MoreObjects.toStringHelper(this).add("method", "notifyTimeUp")
.add("sessionId", sessionId).add("playerListId", playerListId).add("userCode", userCode)
.add("remoteAddress", getRemoteAddress()).toString());
}
@Override
public void notifyGameFinished(int userCode, int oldRating, int newRating, int sessionId)
throws ServiceException {
gameLogger.write(MoreObjects.toStringHelper(this).add("method", "notifyGameFinished")
.add("userCode", userCode).add("sessionId", sessionId).add("oldRating", oldRating)
.add("newRating", newRating).add("remoteAddress", getRemoteAddress()).toString());
}
// ゲームの進行状態を取得する
@Override
public PacketGameStatus getGameStatus(int sessionId) throws ServiceException {
Game session;
try {
session = gameManager.getSession(sessionId);
} catch (GameNotFoundException e) {
String message = "ゲームセッションが見つかりませんでした: sessionId=" + sessionId;
logger.log(Level.WARNING, message, e);
throw new ServiceException(message, e);
}
return session.getGameStatus();
}
@Override
public void keepAliveGame(int sessionId, int playerListId) throws ServiceException {
Game session;
try {
session = gameManager.getSession(sessionId);
} catch (GameNotFoundException e) {
String message = "ゲームセッションが見つかりませんでした: sessionId=" + sessionId;
logger.log(Level.WARNING, message, e);
throw new ServiceException(message, e);
}
session.keepAlive(playerListId);
}
// 結果表示
// 最終結果を取得する
@Override
public List<PacketResult> getResult(int sessionId) throws ServiceException {
Game session;
try {
session = gameManager.getSession(sessionId);
} catch (GameNotFoundException e) {
String message = "ゲームセッションが見つかりませんでした: sessionId=" + sessionId;
logger.log(Level.WARNING, message, e);
throw new ServiceException(message, e);
}
return session.getPacketResult();
}
// チャットにメッセージを送信する
@Override
public void sendMessageToChat(PacketChatMessage chatData) {
int userCode = chatData.userCode;
String remoteAddress = getRemoteAddress();
try {
restrictedUserUtils.checkAndUpdateRestrictedUser(userCode, remoteAddress,
RestrictionType.CHAT);
} catch (DatabaseException e) {
logger.log(Level.INFO, "制限ユーザーのチェックに失敗しました。処理を続行します。", e);
}
// ロックはChatManager側で行う
chatManager.write(chatData, remoteAddress);
}
@Override
public PacketChatMessages receiveMessageFromChat(int lastestResId) {
// ロックはChatManager側で行う
return chatManager.read(lastestResId);
}
// 問題を投稿する
private final Object fileLock = new Object();
@Override
public int uploadProblem(final PacketProblem problem, final int userCode,
boolean resetAnswerCount) throws ServiceException {
final String remoteAddress = getRemoteAddress();
threadPool.execute(new Runnable() {
@Override
public void run() {
synchronized (fileLock) {
try {
Files.createParentDirs(PROBLEM_CREATION_LOG_FILE);
} catch (IOException e) {
logger.log(Level.WARNING, "問題作成ログディレクトリの作成に失敗しました", e);
}
try (PrintStream stream = new PrintStream(
new BufferedOutputStream(new FileOutputStream(PROBLEM_CREATION_LOG_FILE, true)),
false, "MS932")) {
stream.println(problem.toString());
stream
.println(userCode + "\t" + remoteAddress + "\t" + Calendar.getInstance().getTime());
} catch (Exception e) {
logger.log(Level.WARNING, "問題作成ログの書き込みに失敗しました", e);
}
}
}
});
// BugTrack-QMAClone/695 - QMAClone wiki
// http://kishibe.dyndns.tv/qmaclone/wiki/wiki.cgi?page=BugTrack%2DQMAClone%2F695
try {
if (restrictedUserUtils.checkAndUpdateRestrictedUser(userCode, remoteAddress,
RestrictionType.PROBLEM_SUBMITTION)) {
if (problem.id == -1) {
return normalModeProblemManager.getNumberOfProblem() + 1;
} else {
// #699 (ルール違反プレイヤーの報告と指摘に関する要望) – QMAClone
// http://kishibe.dyndns.tv/trac/qmaclone/ticket/699
return problem.id;
}
}
} catch (DatabaseException e) {
logger.log(Level.WARNING, "制限ユーザーの判定に失敗しました");
throw new ServiceException(e);
}
final int problemId;
if (problem.id == -1) {
problemId = wrap("問題の追加に失敗しました", new DatabaseAccessible<Integer>() {
@Override
public Integer access() throws DatabaseException {
return normalModeProblemManager.addProblem(problem);
}
});
} else {
wrap("問題の更新に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
normalModeProblemManager.updateProblem(problem);
return null;
}
});
problemId = problem.id;
}
problem.id = problemId;
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
database.addCreationLog(problem, userCode, remoteAddress);
} catch (DatabaseException e) {
logger.log(Level.WARNING, "問題作成ログの保存に失敗しました", e);
}
}
});
// SNSに投稿
threadPool.execute(new Runnable() {
@Override
public void run() {
snsClient.postProblem(problem);
}
});
// BugTrack-QMAClone/591 - QMAClone wiki
// http://kishibe.dyndns.tv/qmaclone/wiki/wiki.cgi?page=BugTrack%2DQMAClone%2F591
if (resetAnswerCount) {
problemCorrectCounterResetCounter.add(userCode);
}
return problemId;
}
// 問題を取得する
@Override
public List<PacketProblem> getProblemList(final List<Integer> problemIds)
throws ServiceException {
return wrap("問題リストの取得に失敗しました", new DatabaseAccessible<List<PacketProblem>>() {
@Override
public List<PacketProblem> access() throws DatabaseException {
return database.getProblem(problemIds);
}
});
}
@Override
public int[][] getStatisticsOfProblemCount() {
return normalModeProblemManager.getTableProblemCount();
}
@Override
public int[][] getStatisticsOfAccuracyRate() {
return normalModeProblemManager.getTableProblemRatio();
}
@Override
public List<PacketProblem> searchProblem(final String query, final String creator,
final boolean creatorPerfectMatching, final Set<ProblemGenre> genres,
final Set<ProblemType> types, final Set<RandomFlag> randomFlags) throws ServiceException {
return wrap("問題の検索に失敗しました", new DatabaseAccessible<List<PacketProblem>>() {
@Override
public List<PacketProblem> access() throws DatabaseException {
List<PacketProblem> problems = database.searchProblem(query, creator,
creatorPerfectMatching, genres, types, randomFlags);
ImmutableSet<Integer> usedProblems = ImmutableSet
.copyOf(gameManager.getTestingProblemIds());
for (PacketProblem problem : problems) {
problem.testing = usedProblems.contains(problem.id);
}
return problems;
}
});
}
@Override
public List<PacketProblem> searchSimilarProblem(final PacketProblem problem)
throws ServiceException {
return wrap("類似問題の検索に失敗しました", new DatabaseAccessible<List<PacketProblem>>() {
@Override
public List<PacketProblem> access() throws DatabaseException {
return database.searchSimilarProblemFromDatabase(problem);
}
});
}
@Override
public int getNewUserCode() throws ServiceException {
return wrap("新規ユーザーコードの作成に失敗しました", new DatabaseAccessible<Integer>() {
@Override
public Integer access() throws DatabaseException {
int userCode;
do {
userCode = random.nextInt(100000000);
} while (database.isUsedUserCode(userCode));
return userCode;
}
});
}
@Override
public void addProblemIdsToReport(final int userCode, final List<Integer> problemIds)
throws ServiceException {
wrap("正解率統計への問題登録に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.addProblemIdsToReport(userCode, problemIds);
return null;
}
});
}
@Override
public void removeProblemIDFromReport(final int userCode, final int problemID)
throws ServiceException {
wrap("正解率統計からの問題登録解除に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.removeProblemIdFromReport(userCode, problemID);
return null;
}
});
}
@Override
public void clearProblemIDFromReport(final int userCode) throws ServiceException {
wrap("正解率統計のクリアに失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.clearProblemIdFromReport(userCode);
return null;
}
});
}
@Override
public List<PacketProblem> getUserProblemReport(final int userCode) throws ServiceException {
return wrap("正解率統計の読み込みに失敗しました", new DatabaseAccessible<List<PacketProblem>>() {
@Override
public List<PacketProblem> access() throws DatabaseException {
return database.getUserProblemReport(userCode);
}
});
}
@Override
public PacketUserData loadUserData(final int userCode) throws ServiceException {
return wrap("ユーザーデータの読み込みに失敗しました", new DatabaseAccessible<PacketUserData>() {
@Override
public PacketUserData access() throws DatabaseException {
return database.getUserData(userCode);
}
});
}
@Override
public void saveUserData(final PacketUserData userData) throws ServiceException {
wrap("ユーザーデータの保存に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.setUserData(userData);
return null;
}
});
}
@Override
public List<PacketUserData> getLoginUsers() {
return serverStatusManager.getLoginUsers();
}
@Override
public List<PacketWrongAnswer> getWrongAnswers(final int problemID) throws ServiceException {
return wrap("プレイヤー解答の読み込みに失敗しました", new DatabaseAccessible<List<PacketWrongAnswer>>() {
@Override
public List<PacketWrongAnswer> access() throws DatabaseException {
return database.getPlayerAnswers(problemID);
}
});
}
@Override
public void removePlayerAnswers(final int problemID) throws ServiceException {
wrap("プレイヤー解答の削除に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.removePlayerAnswers(problemID);
return null;
}
});
}
@Override
public List<List<PacketRankingData>> getGeneralRanking() throws ServiceException {
return wrap("ランキングデータの読み込みに失敗しました", new DatabaseAccessible<List<List<PacketRankingData>>>() {
@Override
public List<List<PacketRankingData>> access() throws DatabaseException {
return database.getGeneralRankingData();
}
});
}
@Override
public void addIgnoreUserCode(int userCode, int targetUserCode) throws ServiceException {
try {
database.addIgnoreUserCode(userCode, targetUserCode);
} catch (DatabaseException e) {
throw new ServiceException(e);
}
}
@Override
public void removeIgnoreUserCode(int userCode, int targetUserCode) throws ServiceException {
try {
database.removeIgnoreUserCode(userCode, targetUserCode);
} catch (DatabaseException e) {
throw new ServiceException(e);
}
}
@Override
public List<PacketProblemCreationLog> getProblemCreationLog(final int problemId)
throws ServiceException {
return wrap("問題作成ログの取得に失敗しました", new DatabaseAccessible<List<PacketProblemCreationLog>>() {
@Override
public List<PacketProblemCreationLog> access() throws DatabaseException {
return database.getProblemCreationHistory(problemId);
}
});
}
@Override
public List<PacketBbsResponse> getBbsResponses(final int threadId, final int count)
throws ServiceException {
return wrap("BBSレスポンスの取得に失敗しました", new DatabaseAccessible<List<PacketBbsResponse>>() {
@Override
public List<PacketBbsResponse> access() throws DatabaseException {
return database.getBbsResponses(threadId, count);
}
});
}
@Override
public List<PacketBbsThread> getBbsThreads(final int bbsId, final int start, final int count)
throws ServiceException {
return wrap("BBSスレッドの取得に失敗しました", new DatabaseAccessible<List<PacketBbsThread>>() {
@Override
public List<PacketBbsThread> access() throws DatabaseException {
return database.getBbsThreads(bbsId, start, count);
}
});
}
@Override
public void buildBbsThread(final int bbsId, final PacketBbsThread thread,
final PacketBbsResponse response) throws ServiceException {
response.remoteAddress = getRemoteAddress();
int userCode = response.userCode;
String remoteAddress = response.remoteAddress;
try {
if (restrictedUserUtils.checkAndUpdateRestrictedUser(userCode, remoteAddress,
RestrictionType.BBS)) {
return;
}
} catch (DatabaseException e) {
logger.log(Level.INFO, "制限ユーザーのチェックに失敗しました。処理を続行します。", e);
}
wrap("BBSスレッドの設置に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.buildBbsThread(bbsId, thread, response);
return null;
}
});
}
@Override
public void writeToBbs(final PacketBbsResponse response, final boolean age)
throws ServiceException {
response.remoteAddress = getRemoteAddress();
int userCode = response.userCode;
String remoteAddress = response.remoteAddress;
try {
if (restrictedUserUtils.checkAndUpdateRestrictedUser(userCode, remoteAddress,
RestrictionType.BBS)) {
return;
}
} catch (DatabaseException e) {
logger.log(Level.INFO, "制限ユーザーのチェックに失敗しました。処理を続行します。", e);
}
wrap("BBSへの書き込みに失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.writeToBbs(response, age);
return null;
}
});
}
@Override
public int getNumberOfBbsThreads(final int bbsId) throws ServiceException {
return wrap("BBSスレッド数の取得に失敗しました", new DatabaseAccessible<Integer>() {
@Override
public Integer access() throws DatabaseException {
return database.getNumberOfBbsThread(bbsId);
}
});
}
@Override
public int[][] getPrefectureRanking() {
return prefectureRanking.get();
}
@Override
public void addRatingHistory(final int userCode, final int rating) throws ServiceException {
wrap("レーティング履歴の追加に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.addRatingHistory(userCode, rating);
return null;
}
});
}
@Override
public List<Integer> getRatingHistory(final int userCode) throws ServiceException {
return wrap("レーティング履歴の取得に失敗しました", new DatabaseAccessible<List<Integer>>() {
@Override
public List<Integer> access() throws DatabaseException {
return database.getRatingHistory(userCode);
}
});
}
@Override
public PacketRatingDistribution getRatingDistribution() {
return ratingDistribution.get();
}
@Override
public List<List<String>> getThemeModeThemes() {
return themeModeProblemManager.getThemes();
}
@Override
public List<PacketRoomKey> getEventRooms() {
return gameManager.getPublicMatchingEventRooms();
}
@Override
public void voteToProblem(final int userCode, final int problemId, final boolean good,
final String feedback, final String playerName) throws ServiceException {
wrap("問題への投票に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
voteManager.vote(userCode, problemId, good, feedback, playerName, getRemoteAddress());
return null;
}
});
};
@Override
public void resetVote(final int problemId) throws ServiceException {
wrap("良問投票のリセットに失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
voteManager.reset(problemId);
return null;
}
});
}
@Override
public String[] recognizeHandwriting(double[][][] strokes) {
return recognizer.recognize(strokes);
}
@Override
public String getAvailableChalactersForHandwriting() {
return recognizer.getAvailableCharacters();
}
@Override
public void clearProblemFeedback(final int problemId) throws ServiceException {
wrap("問題フィードバックのクリアに失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.clearProblemFeedback(problemId);
return null;
}
});
}
@Override
public List<String> getProblemFeedback(final int problemId) throws ServiceException {
return wrap("問題フィードバックの取得に失敗しました", new DatabaseAccessible<List<String>>() {
@Override
public List<String> access() throws DatabaseException {
return database.getProblemFeedback(problemId);
}
});
}
@Override
public List<PacketTheme> getThemes() throws ServiceException {
List<PacketThemeQuery> themeModeQueries;
try {
themeModeQueries = database.getThemeModeQueries();
} catch (DatabaseException e) {
throw new ServiceException(e);
}
Set<String> themeNames = Sets.newHashSet();
for (PacketThemeQuery themeQuery : themeModeQueries) {
themeNames.add(themeQuery.theme);
}
List<PacketTheme> themes = Lists.newArrayList();
Map<String, IntArray> themesAndProblems = themeModeProblemManager.getThemesAndProblems();
for (String themeName : themeNames) {
PacketTheme theme = new PacketTheme();
theme.setName(themeName);
if (themesAndProblems.containsKey(themeName)) {
theme.setNumberOfProblems(themesAndProblems.get(themeName).size());
}
themes.add(theme);
}
Collections.sort(themes, new Comparator<PacketTheme>() {
@Override
public int compare(PacketTheme o1, PacketTheme o2) {
return o1.getName().compareTo(o2.getName());
}
});
return themes;
}
@Override
public List<PacketThemeQuery> getThemeQueries(final String theme) throws ServiceException {
return wrap("テーマクエリの取得に失敗しました", new DatabaseAccessible<List<PacketThemeQuery>>() {
@Override
public List<PacketThemeQuery> access() throws DatabaseException {
return database.getThemeModeQueries(theme);
}
});
}
@Override
public int getNumberofThemeQueries() throws ServiceException {
return wrap("テーマクエリの数の取得に失敗しました", new DatabaseAccessible<Integer>() {
@Override
public Integer access() throws DatabaseException {
return database.getNumberOfThemeQueries();
}
});
}
private Map<String, AtomicInteger> themeModeNotificationCounter = Maps.newHashMap();
private transient final Runnable commandUpdateThemeModeNotificationCounter = new Runnable() {
@Override
public void run() {
synchronized (themeModeNotificationCounter) {
Set<Entry<String, AtomicInteger>> entrySet = themeModeNotificationCounter.entrySet();
for (Entry<String, AtomicInteger> entry : entrySet) {
if (entry.getValue().decrementAndGet() >= 0) {
continue;
}
final String theme = entry.getKey();
threadPool.execute(new Runnable() {
@Override
public void run() {
snsClient.postThemeModeUpdate(theme);
}
});
entrySet.remove(entry);
}
}
}
};
private static final int THEME_MODE_NOTIFICATION_WAIT = 60;
@Override
public void addThemeModeQuery(final String theme, final String query, final int userCode)
throws ServiceException {
wrap("テーマモードクエリの追加に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.addThemeModeQuery(theme, query);
PacketThemeModeEditLog log = new PacketThemeModeEditLog();
log.setUserCode(userCode);
log.setTimeMs(System.currentTimeMillis());
log.setType(PacketThemeModeEditLog.Type.Add.name());
log.setTheme(theme);
log.setQuery(query);
database.addThemeModeEditLog(log);
return null;
}
});
synchronized (themeModeNotificationCounter) {
themeModeNotificationCounter.put(theme, new AtomicInteger(THEME_MODE_NOTIFICATION_WAIT));
}
}
@Override
public void removeThemeModeQuery(final String theme, final String query, final int userCode)
throws ServiceException {
wrap("テーマモードクエリの削除に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.removeThemeModeQuery(theme, query);
PacketThemeModeEditLog log = new PacketThemeModeEditLog();
log.setUserCode(userCode);
log.setTimeMs(System.currentTimeMillis());
log.setType(PacketThemeModeEditLog.Type.Remove.name());
log.setTheme(theme);
log.setQuery(query);
database.addThemeModeEditLog(log);
return null;
}
});
synchronized (themeModeNotificationCounter) {
themeModeNotificationCounter.put(theme, new AtomicInteger(THEME_MODE_NOTIFICATION_WAIT));
}
}
@Override
public List<PacketThemeModeEditLog> getThemeModeEditLog(final int start, final int length)
throws ServiceException {
return wrap("テーマモード編集ログの取得に失敗しました", new DatabaseAccessible<List<PacketThemeModeEditLog>>() {
@Override
public List<PacketThemeModeEditLog> access() throws DatabaseException {
return database.getThemeModeEditLog(start, length);
}
});
}
@Override
public int getNumberOfThemeModeEditLog() throws ServiceException {
return wrap("テーマモード編集ログの数の取得に失敗しました", new DatabaseAccessible<Integer>() {
@Override
public Integer access() throws DatabaseException {
return database.getNumberOfThemeModeEditLog();
}
});
}
@Override
public boolean isThemeModeEditor(final int userCode) throws ServiceException {
return wrap("テーマモード編集者のチェックに失敗しました", new DatabaseAccessible<Boolean>() {
@Override
public Boolean access() throws DatabaseException {
return themeModeEditorManager.isThemeModeEditor(userCode);
}
});
}
@Override
public void applyThemeModeEditor(final int userCode, final String text) throws ServiceException {
wrap("テーマモード編集者の申請に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
try {
themeModeEditorManager.applyThemeModeEditor(userCode, text);
} catch (MessagingException e) {
logger.log(Level.WARNING, "メールの送信に失敗しました", e);
}
return null;
}
});
}
@Override
public void acceptThemeModeEditor(final int userCode) throws ServiceException {
wrap("テーマモード編集者の承認に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
themeModeEditorManager.acceptThemeModeEditor(userCode);
return null;
}
});
}
@Override
public void rejectThemeModeEditor(final int userCode) throws ServiceException {
wrap("テーマモード編集者の却下に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
themeModeEditorManager.rejectThemeModeEditor(userCode);
return null;
}
});
}
@Override
public List<PacketThemeModeEditor> getThemeModeEditors() throws ServiceException {
return wrap("テーマモード編集者の取得に失敗しました", new DatabaseAccessible<List<PacketThemeModeEditor>>() {
@Override
public List<PacketThemeModeEditor> access() throws DatabaseException {
return themeModeEditorManager.getThemeModeEditors();
}
});
}
@Override
public boolean isApplyingThemeModeEditor(final int userCode) throws ServiceException {
return wrap("テーマモード編集者の申請状態の取得に失敗しました", new DatabaseAccessible<Boolean>() {
@Override
public Boolean access() throws DatabaseException {
return themeModeEditorManager.isApplyingThemeModeEditor(userCode);
}
});
}
@VisibleForTesting
String getRemoteAddress() {
// Proxyを通すとlocalhostが帰ってくる場合があるので、X-Forwarded-Forを優先する
return MoreObjects.firstNonNull(getThreadLocalRequest().getHeader("X-Forwarded-For"),
getThreadLocalRequest().getRemoteAddr());
}
@Override
public int getNumberOfChatLog() throws ServiceException {
return wrap("チャットログの長さの取得に失敗しました", new DatabaseAccessible<Integer>() {
@Override
public Integer access() throws DatabaseException {
return database.getNumberOfChatLog();
}
});
}
@Override
public int getChatLogId(final int year, final int month, final int day, final int hour,
final int minute, final int second) throws ServiceException {
return wrap("チャットログ日時の検索に失敗しました", new DatabaseAccessible<Integer>() {
@Override
public Integer access() throws DatabaseException {
return database.getChatLogId(year, month, day, hour, minute, second);
}
});
}
@Override
public List<PacketChatMessage> getChatLog(final int start) throws ServiceException {
return wrap("チャットログの取得に失敗しました", new DatabaseAccessible<List<PacketChatMessage>>() {
@Override
public List<PacketChatMessage> access() throws DatabaseException {
return database.getChatLog(start);
}
});
}
@Override
public List<PacketImageLink> getWrongImageLinks() throws ServiceException {
return wrap("リンク切れ画像の取得に失敗しました", new DatabaseAccessible<List<PacketImageLink>>() {
@Override
public List<PacketImageLink> access() throws DatabaseException {
return brokenImageLinkDetector.getBrokenImageLinks();
}
});
}
@Override
public boolean canUploadProblem(final int userCode, @Nullable final Integer problemId)
throws ServiceException {
return wrap("問題投稿制限のチェックに失敗しました", new DatabaseAccessible<Boolean>() {
@Override
public Boolean access() throws DatabaseException {
long dateFrom = System.currentTimeMillis() - 60 * 60 * 1000;
boolean newProblem = (problemId == null);
boolean fromAnige = (problemId != null
&& database.getProblem(ImmutableList.of(problemId)).get(0).genre == ProblemGenre.Anige);
int numberOfCreationLogWithMachineIp = database
.getNumberOfCreationLogWithMachineIp(getRemoteAddress(), dateFrom);
int numberOfCreationLogWithUserCode = database.getNumberOfCreationLogWithUserCode(userCode,
dateFrom);
if ((newProblem || !fromAnige)
&& (numberOfCreationLogWithMachineIp > Constant.MAX_NUMBER_OF_CREATION_PER_HOUR
|| numberOfCreationLogWithUserCode > Constant.MAX_NUMBER_OF_CREATION_PER_HOUR)) {
return false;
}
return true;
}
});
}
@Override
public List<PacketProblem> getIndicatedProblems() throws ServiceException {
return wrap("指摘された問題の取得に失敗しました", new DatabaseAccessible<List<PacketProblem>>() {
@Override
public List<PacketProblem> access() throws DatabaseException {
return database.getIndicatedProblems();
}
});
}
@Override
public void indicateProblem(final int problemId, int userCode) throws ServiceException {
try {
if (restrictedUserUtils.checkAndUpdateRestrictedUser(userCode, getRemoteAddress(),
RestrictionType.INDICATION)) {
throw new ServiceException("指摘フラグの更新に失敗しました");
}
} catch (DatabaseException e) {
throw new ServiceException("指摘フラグの更新に失敗しました", e);
}
wrap("指摘フラグの更新に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
List<PacketProblem> problems = database.getProblem(ImmutableList.of(problemId));
PacketProblem problem = problems.get(0);
problem.indication = new Date();
database.updateProblem(problem);
return null;
}
});
problemIndicationCounter.add(userCode);
}
private <T> T wrap(String message, DatabaseAccessible<T> accessor) throws ServiceException {
try {
return accessor.access();
} catch (DatabaseException e) {
logger.log(Level.WARNING, message, e);
throw new ServiceException(Throwables.getStackTraceAsString(e));
}
}
@Override
public boolean resetProblemCorrectCounter(int userCode, int problemId) throws ServiceException {
if (!problemCorrectCounterResetCounter.isAbleToReset(userCode)) {
return false;
}
try {
List<PacketProblem> problems = database.getProblem(ImmutableList.of(problemId));
PacketProblem problem = problems.get(0);
problem.good = problem.bad = 0;
database.updateProblem(problem);
return true;
} catch (DatabaseException e) {
throw new ServiceException(e);
}
}
@Override
public ProblemIndicationEligibility getProblemIndicationEligibility(int userCode)
throws ServiceException {
if (!problemIndicationCounter.isAbleToIndicate(userCode)) {
return ProblemIndicationEligibility.REACHED_MAX_NUMBER_OF_REQUESTS_PER_UNIT_TIME;
}
try {
if (database.getUserData(userCode).playerName.equals("未初期化です")) {
return ProblemIndicationEligibility.PLAYER_NAME_UNCHANGED;
}
} catch (DatabaseException e) {
throw new ServiceException("ユーザー情報の取得に失敗しました", e);
}
return ProblemIndicationEligibility.OK;
}
@Override
public String generateDiffHtml(String before, String after) throws ServiceException {
diff_match_patch differ = new diff_match_patch();
LinkedList<Diff> diffs = differ.diff_main(before, after);
differ.diff_cleanupSemantic(diffs);
String html = differ.diff_prettyHtml(diffs);
html = html.replaceAll("¶", "");
return html;
}
@Override
public void addRestrictedUserCode(final int userCode, final RestrictionType restrictionType)
throws ServiceException {
wrap("制限ユーザーコードの追加に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.addRestrictedUserCode(userCode, restrictionType);
return null;
}
});
}
@Override
public void removeRestrictedUserCode(final int userCode, final RestrictionType restrictionType)
throws ServiceException {
wrap("制限ユーザーコードの削除に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.removeRestrictedUserCode(userCode, restrictionType);
return null;
}
});
}
@Override
public Set<Integer> getRestrictedUserCodes(final RestrictionType restrictionType)
throws ServiceException {
return wrap("制限ユーザーコードの取得に失敗しました", new DatabaseAccessible<Set<Integer>>() {
@Override
public Set<Integer> access() throws DatabaseException {
return database.getRestrictedUserCodes(restrictionType);
}
});
}
@Override
public void clearRestrictedUserCodes(final RestrictionType restrictionType)
throws ServiceException {
wrap("制限ユーザーコードのクリアに失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.clearRestrictedUserCodes(restrictionType);
return null;
}
});
}
@Override
public void addRestrictedRemoteAddress(final String remoteAddress,
final RestrictionType restrictionType) throws ServiceException {
wrap("制限リモートアドレスの追加に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.addRestrictedRemoteAddress(remoteAddress, restrictionType);
return null;
}
});
}
@Override
public void removeRestrictedRemoteAddress(final String remoteAddress,
final RestrictionType restrictionType) throws ServiceException {
wrap("制限リモートアドレスの削除に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.removeRestrictedRemoteAddress(remoteAddress, restrictionType);
return null;
}
});
}
@Override
public Set<String> getRestrictedRemoteAddresses(final RestrictionType restrictionType)
throws ServiceException {
return wrap("制限リモートアドレスの取得に失敗しました", new DatabaseAccessible<Set<String>>() {
@Override
public Set<String> access() throws DatabaseException {
return database.getRestrictedRemoteAddresses(restrictionType);
}
});
}
@Override
public void clearRestrictedRemoteAddresses(final RestrictionType restrictionType)
throws ServiceException {
wrap("制限リモートアドレスのクリアに失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.clearRestrictedRemoteAddresses(restrictionType);
return null;
}
});
}
@Override
public List<PacketRankingData> getThemeRankingOld(final String theme) throws ServiceException {
return wrap("旧テーマモードランキングの取得に失敗しました", new DatabaseAccessible<List<PacketRankingData>>() {
@Override
public List<PacketRankingData> access() throws DatabaseException {
return database.getThemeRankingOld(theme);
}
});
}
@Override
public List<PacketRankingData> getThemeRankingAll(final String theme) throws ServiceException {
return wrap("全テーマモードランキングの取得に失敗しました", new DatabaseAccessible<List<PacketRankingData>>() {
@Override
public List<PacketRankingData> access() throws DatabaseException {
return database.getThemeRankingAll(theme);
}
});
}
@Override
public List<PacketRankingData> getThemeRanking(final String theme, final int year)
throws ServiceException {
return wrap("年別テーマモードランキングの取得に失敗しました", new DatabaseAccessible<List<PacketRankingData>>() {
@Override
public List<PacketRankingData> access() throws DatabaseException {
return database.getThemeRanking(theme, year);
}
});
}
@Override
public List<PacketRankingData> getThemeRanking(final String theme, final int year,
final int month) throws ServiceException {
return wrap("月別テーマモードランキングの取得に失敗しました", new DatabaseAccessible<List<PacketRankingData>>() {
@Override
public List<PacketRankingData> access() throws DatabaseException {
return database.getThemeRanking(theme, year, month);
}
});
}
@Override
public List<PacketMonth> getThemeRankingDateRanges() throws ServiceException {
return wrap("月別テーマモードランキングの取得に失敗しました", new DatabaseAccessible<List<PacketMonth>>() {
@Override
public List<PacketMonth> access() throws DatabaseException {
return database.getThemeRankingDateRanges();
}
});
}
@Override
public List<PacketUserData> lookupUserDataByGooglePlusId(final String googlePlusId)
throws ServiceException {
return wrap("ユーザーコードの検索に失敗しました", new DatabaseAccessible<List<PacketUserData>>() {
@Override
public List<PacketUserData> access() throws DatabaseException {
return database.lookupUserCodeByGooglePlusId(googlePlusId);
}
});
}
@Override
public void disconnectUserCode(final int userCode) throws ServiceException {
wrap("Google+連携の解除に失敗しました", new DatabaseAccessible<Void>() {
@Override
public Void access() throws DatabaseException {
database.disconnectUserCodeFromGooglePlus(userCode);
return null;
}
});
}
}