tv.dyndns.kishibe.qmaclone.server.ServiceServletStub.java Source code

Java tutorial

Introduction

Here is the source code for tv.dyndns.kishibe.qmaclone.server.ServiceServletStub.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;

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("&para;", "");
        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;
            }
        });
    }
}