From 830b8737768a95b02d84ac241e6a0cd1e96401d4 Mon Sep 17 00:00:00 2001 From: rubenlagus Date: Sat, 27 Jul 2019 13:19:54 +0100 Subject: [PATCH] Remove BotLogger --- Bots.ipr | 22 + pom.xml | 12 + .../abilitybots/api/bot/BaseAbilityBot.java | 1601 +++++++++-------- .../abilitybots/api/db/DBContext.java | 2 +- .../abilitybots/api/db/MapDBContext.java | 11 +- .../abilitybots/api/objects/Ability.java | 7 +- .../abilitybots/api/sender/SilentSender.java | 9 +- .../timedbot/TimedSendLongPollingBot.java | 89 +- .../telegrambots/meta/ApiContext.java | 10 +- .../result/chached/package-info.java | 4 - .../TelegramApiRequestException.java | 10 +- .../telegrambots/meta/logging/BotLogger.java | 171 -- .../meta/logging/BotsFileHandler.java | 45 - .../meta/logging/FileFormatter.java | 52 - .../starter/TelegramBotInitializer.java | 29 +- .../updatesreceivers/DefaultBotSession.java | 31 +- .../updatesreceivers/ExponentialBackOff.java | 221 +-- .../updatesreceivers/RestApi.java | 12 +- 18 files changed, 942 insertions(+), 1396 deletions(-) delete mode 100644 telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/inlinequery/result/chached/package-info.java delete mode 100644 telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/BotLogger.java delete mode 100644 telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/BotsFileHandler.java delete mode 100644 telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/FileFormatter.java diff --git a/Bots.ipr b/Bots.ipr index 091115a0..5a589732 100644 --- a/Bots.ipr +++ b/Bots.ipr @@ -1272,6 +1272,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 548e18dc..859d1922 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ 3.0.0 2.23.4 2.9.9 + 2.11.1 @@ -69,6 +70,11 @@ jackson-databind ${jackson.version} + + org.apache.logging.log4j + log4j-core + ${log4j.version} + @@ -93,5 +99,11 @@ ${junit.version} test + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + \ No newline at end of file diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/BaseAbilityBot.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/BaseAbilityBot.java index 529013a4..d5f9c0ef 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/BaseAbilityBot.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/BaseAbilityBot.java @@ -6,9 +6,15 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimap; import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.telegram.abilitybots.api.db.DBContext; -import org.telegram.abilitybots.api.objects.*; +import org.telegram.abilitybots.api.objects.Ability; +import org.telegram.abilitybots.api.objects.Locality; +import org.telegram.abilitybots.api.objects.MessageContext; +import org.telegram.abilitybots.api.objects.Privacy; +import org.telegram.abilitybots.api.objects.Reply; import org.telegram.abilitybots.api.sender.DefaultSender; import org.telegram.abilitybots.api.sender.MessageSender; import org.telegram.abilitybots.api.sender.SilentSender; @@ -26,7 +32,6 @@ import org.telegram.telegrambots.meta.api.objects.Message; import org.telegram.telegrambots.meta.api.objects.Update; import org.telegram.telegrambots.meta.api.objects.User; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; -import org.telegram.telegrambots.meta.logging.BotLogger; import java.io.File; import java.io.FileNotFoundException; @@ -34,8 +39,13 @@ import java.io.FileReader; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; @@ -55,12 +65,45 @@ import static java.util.regex.Pattern.compile; import static java.util.stream.Collectors.joining; import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.telegram.abilitybots.api.objects.Ability.builder; -import static org.telegram.abilitybots.api.objects.Flag.*; -import static org.telegram.abilitybots.api.objects.Locality.*; +import static org.telegram.abilitybots.api.objects.Flag.DOCUMENT; +import static org.telegram.abilitybots.api.objects.Flag.MESSAGE; +import static org.telegram.abilitybots.api.objects.Flag.REPLY; +import static org.telegram.abilitybots.api.objects.Locality.ALL; +import static org.telegram.abilitybots.api.objects.Locality.GROUP; +import static org.telegram.abilitybots.api.objects.Locality.USER; import static org.telegram.abilitybots.api.objects.MessageContext.newContext; -import static org.telegram.abilitybots.api.objects.Privacy.*; -import static org.telegram.abilitybots.api.util.AbilityMessageCodes.*; -import static org.telegram.abilitybots.api.util.AbilityUtils.*; +import static org.telegram.abilitybots.api.objects.Privacy.ADMIN; +import static org.telegram.abilitybots.api.objects.Privacy.CREATOR; +import static org.telegram.abilitybots.api.objects.Privacy.GROUP_ADMIN; +import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_BAN_FAIL; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_BAN_SUCCESS; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_CLAIM_FAIL; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_CLAIM_SUCCESS; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_COMMANDS_NOT_FOUND; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_DEMOTE_FAIL; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_DEMOTE_SUCCESS; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_PROMOTE_FAIL; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_PROMOTE_SUCCESS; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_ERROR; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_FAIL; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_MESSAGE; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_SUCCESS; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_UNBAN_FAIL; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_UNBAN_SUCCESS; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.CHECK_INPUT_FAIL; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.CHECK_LOCALITY_FAIL; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.CHECK_PRIVACY_FAIL; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.USER_NOT_FOUND; +import static org.telegram.abilitybots.api.util.AbilityUtils.addTag; +import static org.telegram.abilitybots.api.util.AbilityUtils.commitTo; +import static org.telegram.abilitybots.api.util.AbilityUtils.getChatId; +import static org.telegram.abilitybots.api.util.AbilityUtils.getLocalizedMessage; +import static org.telegram.abilitybots.api.util.AbilityUtils.isGroupUpdate; +import static org.telegram.abilitybots.api.util.AbilityUtils.isSuperGroupUpdate; +import static org.telegram.abilitybots.api.util.AbilityUtils.isUserMessage; +import static org.telegram.abilitybots.api.util.AbilityUtils.shortName; +import static org.telegram.abilitybots.api.util.AbilityUtils.stripTag; /** * The father of all ability bots. Bots that need to utilize abilities need to extend this bot. @@ -96,812 +139,812 @@ import static org.telegram.abilitybots.api.util.AbilityUtils.*; */ @SuppressWarnings({"ConfusingArgumentToVarargsMethod", "UnusedReturnValue", "WeakerAccess", "unused", "ConstantConditions"}) public abstract class BaseAbilityBot extends DefaultAbsSender implements AbilityExtension { - private static final String TAG = BaseAbilityBot.class.getSimpleName(); + private static final Logger log = LogManager.getLogger(BaseAbilityBot.class); - // DB objects - public static final String ADMINS = "ADMINS"; - public static final String USERS = "USERS"; - public static final String USER_ID = "USER_ID"; - public static final String BLACKLIST = "BLACKLIST"; + // DB objects + public static final String ADMINS = "ADMINS"; + public static final String USERS = "USERS"; + public static final String USER_ID = "USER_ID"; + public static final String BLACKLIST = "BLACKLIST"; - // Factory commands - protected static final String DEFAULT = "default"; - protected static final String CLAIM = "claim"; - protected static final String BAN = "ban"; - protected static final String PROMOTE = "promote"; - protected static final String DEMOTE = "demote"; - protected static final String UNBAN = "unban"; - protected static final String BACKUP = "backup"; - protected static final String RECOVER = "recover"; - protected static final String COMMANDS = "commands"; - protected static final String REPORT = "report"; + // Factory commands + protected static final String DEFAULT = "default"; + protected static final String CLAIM = "claim"; + protected static final String BAN = "ban"; + protected static final String PROMOTE = "promote"; + protected static final String DEMOTE = "demote"; + protected static final String UNBAN = "unban"; + protected static final String BACKUP = "backup"; + protected static final String RECOVER = "recover"; + protected static final String COMMANDS = "commands"; + protected static final String REPORT = "report"; - // DB and sender - protected final DBContext db; - protected MessageSender sender; - protected SilentSender silent; + // DB and sender + protected final DBContext db; + protected MessageSender sender; + protected SilentSender silent; - // Bot token and username - private final String botToken; - private final String botUsername; + // Bot token and username + private final String botToken; + private final String botUsername; - // Ability registry - private Map abilities; + // Ability registry + private Map abilities; - // Reply registry - private List replies; + // Reply registry + private List replies; - public abstract int creatorId(); + public abstract int creatorId(); - protected BaseAbilityBot(String botToken, String botUsername, DBContext db, DefaultBotOptions botOptions) { - super(botOptions); + protected BaseAbilityBot(String botToken, String botUsername, DBContext db, DefaultBotOptions botOptions) { + super(botOptions); - this.botToken = botToken; - this.botUsername = botUsername; - this.db = db; - this.sender = new DefaultSender(this); - silent = new SilentSender(sender); + this.botToken = botToken; + this.botUsername = botUsername; + this.db = db; + this.sender = new DefaultSender(this); + silent = new SilentSender(sender); - registerAbilities(); - } - - /** - * @return the map of ID -> User - */ - protected Map users() { - return db.getMap(USERS); - } - - /** - * @return the map of Username -> ID - */ - protected Map userIds() { - return db.getMap(USER_ID); - } - - /** - * @return a blacklist containing all the IDs of the banned users - */ - protected Set blacklist() { - return db.getSet(BLACKLIST); - } - - /** - * @return an admin set of all the IDs of bot administrators - */ - protected Set admins() { - return db.getSet(ADMINS); - } - - /** - * @return the immutable map of String -> Ability - */ - public Map abilities() { - return abilities; - } - - /** - * @return the immutable list carrying the embedded replies - */ - public List replies() { - return replies; - } - - /** - * This method contains the stream of actions that are applied on any update. - *

- * It will correctly handle addition of users into the DB and the execution of abilities and replies. - * - * @param update the update received by Telegram's API - */ - public void onUpdateReceived(Update update) { - BotLogger.info(format("New update [%s] received at %s", update.getUpdateId(), now()), format("%s - %s", TAG, botUsername)); - BotLogger.info(update.toString(), TAG); - long millisStarted = System.currentTimeMillis(); - - Stream.of(update) - .filter(this::checkGlobalFlags) - .filter(this::checkBlacklist) - .map(this::addUser) - .filter(this::filterReply) - .map(this::getAbility) - .filter(this::validateAbility) - .filter(this::checkPrivacy) - .filter(this::checkLocality) - .filter(this::checkInput) - .filter(this::checkMessageFlags) - .map(this::getContext) - .map(this::consumeUpdate) - .forEach(this::postConsumption); - - long processingTime = System.currentTimeMillis() - millisStarted; - BotLogger.info(format("Processing of update [%s] ended at %s%n---> Processing time: [%d ms] <---%n", update.getUpdateId(), now(), processingTime), format("%s - %s", TAG, botUsername)); - } - - public String getBotToken() { - return botToken; - } - - public String getBotUsername() { - return botUsername; - } - - /** - * Test the update against the provided global flags. The default implementation is a passthrough to all updates. - *

- * This method should be overridden if the user wants to restrict bot usage to only certain updates. - * - * @param update a Telegram {@link Update} - * @return true if the update satisfies the global flags - */ - protected boolean checkGlobalFlags(Update update) { - return true; - } - - /** - * Gets the user with the specified username. - * - * @param username the username of the required user - * @return the user - */ - protected User getUser(String username) { - Integer id = userIds().get(username.toLowerCase()); - if (id == null) { - throw new IllegalStateException(format("Could not find ID corresponding to username [%s]", username)); + registerAbilities(); } - return getUser(id); - } - - /** - * Gets the user with the specified ID. - * - * @param id the id of the required user - * @return the user - */ - protected User getUser(int id) { - User user = users().get(id); - if (user == null) { - throw new IllegalStateException(format("Could not find user corresponding to id [%d]", id)); + /** + * @return the map of + */ + protected Map users() { + return db.getMap(USERS); } - return user; - } - - /** - * Gets the user with the specified username. If user was not found, the bot will send a message on Telegram. - * - * @param username the username of the required user - * @param ctx the message context with the originating user - * @return the id of the user - */ - protected int getUserIdSendError(String username, MessageContext ctx) { - try { - return getUser(username).getId(); - } catch (IllegalStateException ex) { - silent.send(getLocalizedMessage(USER_NOT_FOUND, ctx.user().getLanguageCode(), username), ctx.chatId()); - throw ex; + /** + * @return the map of + */ + protected Map userIds() { + return db.getMap(USER_ID); } - } - /** - *

- * Format of the report: - *

- * [command1] - [description1] - *

- * [command2] - [description2] - *

- * ... - *

- * Once you invoke it, the bot will send the available commands to the chat. This is a public ability so anyone can invoke it. - *

- * Usage: /commands - * - * @return the ability to report commands defined by the child bot. - */ - public Ability reportCommands() { - return builder() - .name(REPORT) - .locality(ALL) - .privacy(CREATOR) - .input(0) - .action(ctx -> { - String commands = abilities.values().stream() - .filter(ability -> nonNull(ability.info())) - .map(ability -> { - String name = ability.name(); - String info = ability.info(); - return format("%s - %s", name, info); - }) - .sorted() - .reduce((a, b) -> format("%s%n%s", a, b)) - .orElse(getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode())); + /** + * @return a blacklist containing all the IDs of the banned users + */ + protected Set blacklist() { + return db.getSet(BLACKLIST); + } - silent.send(commands, ctx.chatId()); - }) - .build(); - } + /** + * @return an admin set of all the IDs of bot administrators + */ + protected Set admins() { + return db.getSet(ADMINS); + } - /** - * Default format: - *

- * PUBLIC - *

- * [command1] - [description1] - *

- * [command2] - [description2] - *

- * GROUP_ADMIN - *

- * [command1] - [description1] - *

- * ... - * - * @return the ability to print commands based on the privacy of the requesting user - */ - public Ability commands() { - return builder() - .name(COMMANDS) - .locality(USER) - .privacy(PUBLIC) - .input(0) - .action(ctx -> { - Privacy privacy = getPrivacy(ctx.update(), ctx.user().getId()); + /** + * @return the immutable map of + */ + public Map abilities() { + return abilities; + } - ListMultimap abilitiesPerPrivacy = abilities.values().stream() - .map(ability -> { - String name = ability.name(); - String info = ability.info(); + /** + * @return the immutable list carrying the embedded replies + */ + public List replies() { + return replies; + } - if (!isEmpty(info)) - return Pair.of(ability.privacy(), format("/%s - %s", name, info)); - return Pair.of(ability.privacy(), format("/%s", name)); - }) - .sorted(comparing(Pair::b)) - .collect(() -> hashKeys().arrayListValues().build(), - (map, pair) -> map.put(pair.a(), pair.b()), - Multimap::putAll); + /** + * This method contains the stream of actions that are applied on any update. + *

+ * It will correctly handle addition of users into the DB and the execution of abilities and replies. + * + * @param update the update received by Telegram's API + */ + public void onUpdateReceived(Update update) { + log.info(format("[%s] New update [%s] received at %s", botUsername, update.getUpdateId(), now())); + log.info(update.toString()); + long millisStarted = System.currentTimeMillis(); - String commands = abilitiesPerPrivacy.asMap().entrySet().stream() - .filter(entry -> privacy.compareTo(entry.getKey()) >= 0) - .sorted(comparing(Entry::getKey)) - .map(entry -> - entry.getValue().stream() - .reduce(entry.getKey().toString(), (a, b) -> format("%s\n%s", a, b)) - ) - .collect(joining("\n")); + Stream.of(update) + .filter(this::checkGlobalFlags) + .filter(this::checkBlacklist) + .map(this::addUser) + .filter(this::filterReply) + .map(this::getAbility) + .filter(this::validateAbility) + .filter(this::checkPrivacy) + .filter(this::checkLocality) + .filter(this::checkInput) + .filter(this::checkMessageFlags) + .map(this::getContext) + .map(this::consumeUpdate) + .forEach(this::postConsumption); - if (commands.isEmpty()) - commands = getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode()); + long processingTime = System.currentTimeMillis() - millisStarted; + log.info(format("[%s] Processing of update [%s] ended at %s%n---> Processing time: [%d ms] <---%n", botUsername, update.getUpdateId(), now(), processingTime)); + } - silent.send(commands, ctx.chatId()); - }) - .build(); - } + public String getBotToken() { + return botToken; + } - /** - * This backup ability returns the object defined by {@link DBContext#backup()} as a message document. - *

- * This is a high-profile ability and is restricted to the CREATOR only. - *

- * Usage: /backup - * - * @return the ability to back-up the database of the bot - */ - public Ability backupDB() { - return builder() - .name(BACKUP) - .locality(USER) - .privacy(CREATOR) - .input(0) - .action(ctx -> { - File backup = new File("backup.json"); + public String getBotUsername() { + return botUsername; + } - try (PrintStream printStream = new PrintStream(backup)) { - printStream.print(db.backup()); - sender.sendDocument(new SendDocument() - .setDocument(backup) - .setChatId(ctx.chatId()) - ); - } catch (FileNotFoundException e) { - BotLogger.error("Error while fetching backup", TAG, e); - } catch (TelegramApiException e) { - BotLogger.error("Error while sending document/backup file", TAG, e); - } - }) - .build(); - } + /** + * Test the update against the provided global flags. The default implementation is a passthrough to all updates. + *

+ * This method should be overridden if the user wants to restrict bot usage to only certain updates. + * + * @param update a Telegram {@link Update} + * @return true if the update satisfies the global flags + */ + protected boolean checkGlobalFlags(Update update) { + return true; + } - /** - * Recovers the bot database using {@link DBContext#recover(Object)}. - *

- * The bot recovery process hugely depends on the implementation of the recovery method of {@link DBContext}. - *

- * Usage: /recover - * - * @return the ability to recover the database of the bot - */ - public Ability recoverDB() { - return builder() - .name(RECOVER) - .locality(USER) - .privacy(CREATOR) - .input(0) - .action(ctx -> silent.forceReply( - getLocalizedMessage(ABILITY_RECOVER_MESSAGE, ctx.user().getLanguageCode()), ctx.chatId())) - .reply(update -> { - String replyToMsg = update.getMessage().getReplyToMessage().getText(); - String recoverMessage = getLocalizedMessage(ABILITY_RECOVER_MESSAGE, AbilityUtils.getUser(update).getLanguageCode()); - if (!replyToMsg.equals(recoverMessage)) - return; + /** + * Gets the user with the specified username. + * + * @param username the username of the required user + * @return the user + */ + protected User getUser(String username) { + Integer id = userIds().get(username.toLowerCase()); + if (id == null) { + throw new IllegalStateException(format("Could not find ID corresponding to username [%s]", username)); + } - String fileId = update.getMessage().getDocument().getFileId(); - try (FileReader reader = new FileReader(downloadFileWithId(fileId))) { - String backupData = IOUtils.toString(reader); - if (db.recover(backupData)) { - send(ABILITY_RECOVER_SUCCESS, update); - } else { - send(ABILITY_RECOVER_FAIL, update); + return getUser(id); + } + + /** + * Gets the user with the specified ID. + * + * @param id the id of the required user + * @return the user + */ + protected User getUser(int id) { + User user = users().get(id); + if (user == null) { + throw new IllegalStateException(format("Could not find user corresponding to id [%d]", id)); + } + + return user; + } + + /** + * Gets the user with the specified username. If user was not found, the bot will send a message on Telegram. + * + * @param username the username of the required user + * @param ctx the message context with the originating user + * @return the id of the user + */ + protected int getUserIdSendError(String username, MessageContext ctx) { + try { + return getUser(username).getId(); + } catch (IllegalStateException ex) { + silent.send(getLocalizedMessage(USER_NOT_FOUND, ctx.user().getLanguageCode(), username), ctx.chatId()); + throw ex; + } + } + + /** + *

+ * Format of the report: + *

+ * [command1] - [description1] + *

+ * [command2] - [description2] + *

+ * ... + *

+ * Once you invoke it, the bot will send the available commands to the chat. This is a public ability so anyone can invoke it. + *

+ * Usage: /commands + * + * @return the ability to report commands defined by the child bot. + */ + public Ability reportCommands() { + return builder() + .name(REPORT) + .locality(ALL) + .privacy(CREATOR) + .input(0) + .action(ctx -> { + String commands = abilities.values().stream() + .filter(ability -> nonNull(ability.info())) + .map(ability -> { + String name = ability.name(); + String info = ability.info(); + return format("%s - %s", name, info); + }) + .sorted() + .reduce((a, b) -> format("%s%n%s", a, b)) + .orElse(getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode())); + + silent.send(commands, ctx.chatId()); + }) + .build(); + } + + /** + * Default format: + *

+ * PUBLIC + *

+ * [command1] - [description1] + *

+ * [command2] - [description2] + *

+ * GROUP_ADMIN + *

+ * [command1] - [description1] + *

+ * ... + * + * @return the ability to print commands based on the privacy of the requesting user + */ + public Ability commands() { + return builder() + .name(COMMANDS) + .locality(USER) + .privacy(PUBLIC) + .input(0) + .action(ctx -> { + Privacy privacy = getPrivacy(ctx.update(), ctx.user().getId()); + + ListMultimap abilitiesPerPrivacy = abilities.values().stream() + .map(ability -> { + String name = ability.name(); + String info = ability.info(); + + if (!isEmpty(info)) + return Pair.of(ability.privacy(), format("/%s - %s", name, info)); + return Pair.of(ability.privacy(), format("/%s", name)); + }) + .sorted(comparing(Pair::b)) + .collect(() -> hashKeys().arrayListValues().build(), + (map, pair) -> map.put(pair.a(), pair.b()), + Multimap::putAll); + + String commands = abilitiesPerPrivacy.asMap().entrySet().stream() + .filter(entry -> privacy.compareTo(entry.getKey()) >= 0) + .sorted(comparing(Entry::getKey)) + .map(entry -> + entry.getValue().stream() + .reduce(entry.getKey().toString(), (a, b) -> format("%s\n%s", a, b)) + ) + .collect(joining("\n")); + + if (commands.isEmpty()) + commands = getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode()); + + silent.send(commands, ctx.chatId()); + }) + .build(); + } + + /** + * This backup ability returns the object defined by {@link DBContext#backup()} as a message document. + *

+ * This is a high-profile ability and is restricted to the CREATOR only. + *

+ * Usage: /backup + * + * @return the ability to back-up the database of the bot + */ + public Ability backupDB() { + return builder() + .name(BACKUP) + .locality(USER) + .privacy(CREATOR) + .input(0) + .action(ctx -> { + File backup = new File("backup.json"); + + try (PrintStream printStream = new PrintStream(backup)) { + printStream.print(db.backup()); + sender.sendDocument(new SendDocument() + .setDocument(backup) + .setChatId(ctx.chatId()) + ); + } catch (FileNotFoundException e) { + log.error("Error while fetching backup", e); + } catch (TelegramApiException e) { + log.error("Error while sending document/backup file", e); + } + }) + .build(); + } + + /** + * Recovers the bot database using {@link DBContext#recover(Object)}. + *

+ * The bot recovery process hugely depends on the implementation of the recovery method of {@link DBContext}. + *

+ * Usage: /recover + * + * @return the ability to recover the database of the bot + */ + public Ability recoverDB() { + return builder() + .name(RECOVER) + .locality(USER) + .privacy(CREATOR) + .input(0) + .action(ctx -> silent.forceReply( + getLocalizedMessage(ABILITY_RECOVER_MESSAGE, ctx.user().getLanguageCode()), ctx.chatId())) + .reply(update -> { + String replyToMsg = update.getMessage().getReplyToMessage().getText(); + String recoverMessage = getLocalizedMessage(ABILITY_RECOVER_MESSAGE, AbilityUtils.getUser(update).getLanguageCode()); + if (!replyToMsg.equals(recoverMessage)) + return; + + String fileId = update.getMessage().getDocument().getFileId(); + try (FileReader reader = new FileReader(downloadFileWithId(fileId))) { + String backupData = IOUtils.toString(reader); + if (db.recover(backupData)) { + send(ABILITY_RECOVER_SUCCESS, update); + } else { + send(ABILITY_RECOVER_FAIL, update); + } + } catch (Exception e) { + log.error("Could not recover DB from backup", e); + send(ABILITY_RECOVER_ERROR, update); + } + }, MESSAGE, DOCUMENT, REPLY) + .build(); + } + + /** + * Banned users are accumulated in the blacklist. Use {@link DBContext#getSet(String)} with name specified by {@link BaseAbilityBot#BLACKLIST}. + *

+ * Usage: /ban @username + *

+ * Note that admins who try to ban the creator, get banned. + * + * @return the ability to ban the user from any kind of bot interaction + */ + public Ability banUser() { + return builder() + .name(BAN) + .locality(ALL) + .privacy(ADMIN) + .input(1) + .action(ctx -> { + String username = stripTag(ctx.firstArg()); + int userId = getUserIdSendError(username, ctx); + String bannedUser; + + // Protection from abuse + if (userId == creatorId()) { + userId = ctx.user().getId(); + bannedUser = isNullOrEmpty(ctx.user().getUserName()) ? addTag(ctx.user().getUserName()) : shortName(ctx.user()); + } else { + bannedUser = addTag(username); + } + + Set blacklist = blacklist(); + if (blacklist.contains(userId)) + sendMd(ABILITY_BAN_FAIL, ctx, escape(bannedUser)); + else { + blacklist.add(userId); + sendMd(ABILITY_BAN_SUCCESS, ctx, escape(bannedUser)); + } + }) + .post(commitTo(db)) + .build(); + } + + /** + * Usage: /unban @username + * + * @return the ability to unban a user + */ + public Ability unbanUser() { + return builder() + .name(UNBAN) + .locality(ALL) + .privacy(ADMIN) + .input(1) + .action(ctx -> { + String username = stripTag(ctx.firstArg()); + Integer userId = getUserIdSendError(username, ctx); + + Set blacklist = blacklist(); + + if (!blacklist.remove(userId)) + silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_FAIL, ctx.user().getLanguageCode(), escape(username)), ctx.chatId()); + else { + silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_SUCCESS, ctx.user().getLanguageCode(), escape(username)), ctx.chatId()); + } + }) + .post(commitTo(db)) + .build(); + } + + /** + * @return the ability to promote a user to a bot admin + */ + public Ability promoteAdmin() { + return builder() + .name(PROMOTE) + .locality(ALL) + .privacy(ADMIN) + .input(1) + .action(ctx -> { + String username = stripTag(ctx.firstArg()); + Integer userId = getUserIdSendError(username, ctx); + + Set admins = admins(); + if (admins.contains(userId)) + sendMd(ABILITY_PROMOTE_FAIL, ctx, escape(username)); + else { + admins.add(userId); + sendMd(ABILITY_PROMOTE_SUCCESS, ctx, escape(username)); + } + }).post(commitTo(db)) + .build(); + } + + /** + * @return the ability to demote an admin to a user + */ + public Ability demoteAdmin() { + return builder() + .name(DEMOTE) + .locality(ALL) + .privacy(ADMIN) + .input(1) + .action(ctx -> { + String username = stripTag(ctx.firstArg()); + Integer userId = getUserIdSendError(username, ctx); + + Set admins = admins(); + if (admins.remove(userId)) { + sendMd(ABILITY_DEMOTE_SUCCESS, ctx, escape(username)); + } else { + sendMd(ABILITY_DEMOTE_FAIL, ctx, escape(username)); + } + }) + .post(commitTo(db)) + .build(); + } + + /** + * Regular users and admins who try to claim the bot will get banned. + * + * @return the ability to claim yourself as the master and creator of the bot + */ + public Ability claimCreator() { + return builder() + .name(CLAIM) + .locality(ALL) + .privacy(CREATOR) + .input(0) + .action(ctx -> { + Set admins = admins(); + int id = creatorId(); + + if (admins.contains(id)) + send(ABILITY_CLAIM_FAIL, ctx); + else { + admins.add(id); + send(ABILITY_CLAIM_SUCCESS, ctx); + } + }) + .post(commitTo(db)) + .build(); + } + + private Optional send(String message, MessageContext ctx, String... args) { + return silent.send(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId()); + } + + private Optional sendMd(String message, MessageContext ctx, String... args) { + return silent.sendMd(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId()); + } + + private Optional send(String message, Update upd) { + Long chatId = upd.getMessage().getChatId(); + return silent.send(getLocalizedMessage(message, AbilityUtils.getUser(upd).getLanguageCode()), chatId); + } + + /** + * Registers the declared abilities using method reflection. Also, replies are accumulated using the built abilities and standalone methods that return a Reply. + *

+ * Only abilities and replies with the public accessor are registered! + */ + private void registerAbilities() { + try { + // Collect all classes that implement AbilityExtension declared in the bot + List extensions = stream(getClass().getMethods()) + .filter(checkReturnType(AbilityExtension.class)) + .map(returnExtension(this)) + .collect(Collectors.toList()); + + // Add the bot itself as it is an AbilityExtension + extensions.add(this); + + // Extract all abilities from every single extension instance + abilities = extensions.stream() + .flatMap(ext -> stream(ext.getClass().getMethods()) + .filter(checkReturnType(Ability.class)) + .map(returnAbility(ext))) + // Abilities are immutable, build it respectively + .collect(ImmutableMap::builder, + (b, a) -> b.put(a.name(), a), + (b1, b2) -> b1.putAll(b2.build())) + .build(); + + // Extract all replies from every single extension instance + Stream extensionReplies = extensions.stream() + .flatMap(ext -> stream(ext.getClass().getMethods()) + .filter(checkReturnType(Reply.class)) + .map(returnReply(ext))); + + // Replies can be standalone or attached to abilities, fetch those too + Stream abilityReplies = abilities.values().stream() + .flatMap(ability -> ability.replies().stream()); + + // Now create the replies registry (list) + replies = Stream.concat(abilityReplies, extensionReplies).collect( + ImmutableList::builder, + Builder::add, + (b1, b2) -> b1.addAll(b2.build())) + .build(); + } catch (IllegalStateException e) { + log.error("Duplicate names found while registering abilities. Make sure that the abilities declared don't clash with the reserved ones.", e); + throw new RuntimeException(e); + } + } + + /** + * @param clazz the type to be tested + * @return a predicate testing the return type of the method corresponding to the class parameter + */ + private Predicate checkReturnType(Class clazz) { + return method -> clazz.isAssignableFrom(method.getReturnType()); + } + + /** + * Invokes the method and retrieves its return {@link Reply}. + * + * @param obj an bot or extension that this method is invoked with + * @return a {@link Function} which returns the {@link Reply} returned by the given method + */ + private Function returnExtension(Object obj) { + return method -> { + try { + return (AbilityExtension) method.invoke(obj); + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("Could not add ability extension", e); + throw new RuntimeException(e); } - } catch (Exception e) { - BotLogger.error("Could not recover DB from backup", TAG, e); - send(ABILITY_RECOVER_ERROR, update); - } - }, MESSAGE, DOCUMENT, REPLY) - .build(); - } - - /** - * Banned users are accumulated in the blacklist. Use {@link DBContext#getSet(String)} with name specified by {@link BaseAbilityBot#BLACKLIST}. - *

- * Usage: /ban @username - *

- * Note that admins who try to ban the creator, get banned. - * - * @return the ability to ban the user from any kind of bot interaction - */ - public Ability banUser() { - return builder() - .name(BAN) - .locality(ALL) - .privacy(ADMIN) - .input(1) - .action(ctx -> { - String username = stripTag(ctx.firstArg()); - int userId = getUserIdSendError(username, ctx); - String bannedUser; - - // Protection from abuse - if (userId == creatorId()) { - userId = ctx.user().getId(); - bannedUser = isNullOrEmpty(ctx.user().getUserName()) ? addTag(ctx.user().getUserName()) : shortName(ctx.user()); - } else { - bannedUser = addTag(username); - } - - Set blacklist = blacklist(); - if (blacklist.contains(userId)) - sendMd(ABILITY_BAN_FAIL, ctx, escape(bannedUser)); - else { - blacklist.add(userId); - sendMd(ABILITY_BAN_SUCCESS, ctx, escape(bannedUser)); - } - }) - .post(commitTo(db)) - .build(); - } - - /** - * Usage: /unban @username - * - * @return the ability to unban a user - */ - public Ability unbanUser() { - return builder() - .name(UNBAN) - .locality(ALL) - .privacy(ADMIN) - .input(1) - .action(ctx -> { - String username = stripTag(ctx.firstArg()); - Integer userId = getUserIdSendError(username, ctx); - - Set blacklist = blacklist(); - - if (!blacklist.remove(userId)) - silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_FAIL, ctx.user().getLanguageCode(), escape(username)), ctx.chatId()); - else { - silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_SUCCESS, ctx.user().getLanguageCode(), escape(username)), ctx.chatId()); - } - }) - .post(commitTo(db)) - .build(); - } - - /** - * @return the ability to promote a user to a bot admin - */ - public Ability promoteAdmin() { - return builder() - .name(PROMOTE) - .locality(ALL) - .privacy(ADMIN) - .input(1) - .action(ctx -> { - String username = stripTag(ctx.firstArg()); - Integer userId = getUserIdSendError(username, ctx); - - Set admins = admins(); - if (admins.contains(userId)) - sendMd(ABILITY_PROMOTE_FAIL, ctx, escape(username)); - else { - admins.add(userId); - sendMd(ABILITY_PROMOTE_SUCCESS, ctx, escape(username)); - } - }).post(commitTo(db)) - .build(); - } - - /** - * @return the ability to demote an admin to a user - */ - public Ability demoteAdmin() { - return builder() - .name(DEMOTE) - .locality(ALL) - .privacy(ADMIN) - .input(1) - .action(ctx -> { - String username = stripTag(ctx.firstArg()); - Integer userId = getUserIdSendError(username, ctx); - - Set admins = admins(); - if (admins.remove(userId)) { - sendMd(ABILITY_DEMOTE_SUCCESS, ctx, escape(username)); - } else { - sendMd(ABILITY_DEMOTE_FAIL, ctx, escape(username)); - } - }) - .post(commitTo(db)) - .build(); - } - - /** - * Regular users and admins who try to claim the bot will get banned. - * - * @return the ability to claim yourself as the master and creator of the bot - */ - public Ability claimCreator() { - return builder() - .name(CLAIM) - .locality(ALL) - .privacy(CREATOR) - .input(0) - .action(ctx -> { - Set admins = admins(); - int id = creatorId(); - - if (admins.contains(id)) - send(ABILITY_CLAIM_FAIL, ctx); - else { - admins.add(id); - send(ABILITY_CLAIM_SUCCESS, ctx); - } - }) - .post(commitTo(db)) - .build(); - } - - private Optional send(String message, MessageContext ctx, String... args) { - return silent.send(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId()); - } - - private Optional sendMd(String message, MessageContext ctx, String... args) { - return silent.sendMd(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId()); - } - - private Optional send(String message, Update upd) { - Long chatId = upd.getMessage().getChatId(); - return silent.send(getLocalizedMessage(message, AbilityUtils.getUser(upd).getLanguageCode()), chatId); - } - - /** - * Registers the declared abilities using method reflection. Also, replies are accumulated using the built abilities and standalone methods that return a Reply. - *

- * Only abilities and replies with the public accessor are registered! - */ - private void registerAbilities() { - try { - // Collect all classes that implement AbilityExtension declared in the bot - List extensions = stream(getClass().getMethods()) - .filter(checkReturnType(AbilityExtension.class)) - .map(returnExtension(this)) - .collect(Collectors.toList()); - - // Add the bot itself as it is an AbilityExtension - extensions.add(this); - - // Extract all abilities from every single extension instance - abilities = extensions.stream() - .flatMap(ext -> stream(ext.getClass().getMethods()) - .filter(checkReturnType(Ability.class)) - .map(returnAbility(ext))) - // Abilities are immutable, build it respectively - .collect(ImmutableMap::builder, - (b, a) -> b.put(a.name(), a), - (b1, b2) -> b1.putAll(b2.build())) - .build(); - - // Extract all replies from every single extension instance - Stream extensionReplies = extensions.stream() - .flatMap(ext -> stream(ext.getClass().getMethods()) - .filter(checkReturnType(Reply.class)) - .map(returnReply(ext))); - - // Replies can be standalone or attached to abilities, fetch those too - Stream abilityReplies = abilities.values().stream() - .flatMap(ability -> ability.replies().stream()); - - // Now create the replies registry (list) - replies = Stream.concat(abilityReplies, extensionReplies).collect( - ImmutableList::builder, - Builder::add, - (b1, b2) -> b1.addAll(b2.build())) - .build(); - } catch (IllegalStateException e) { - BotLogger.error(TAG, "Duplicate names found while registering abilities. Make sure that the abilities declared don't clash with the reserved ones.", e); - throw new RuntimeException(e); - } - } - - /** - * @param clazz the type to be tested - * @return a predicate testing the return type of the method corresponding to the class parameter - */ - private Predicate checkReturnType(Class clazz) { - return method -> clazz.isAssignableFrom(method.getReturnType()); - } - - /** - * Invokes the method and retrieves its return {@link Reply}. - * - * @param obj an bot or extension that this method is invoked with - * @return a {@link Function} which returns the {@link Reply} returned by the given method - */ - private Function returnExtension(Object obj) { - return method -> { - try { - return (AbilityExtension) method.invoke(obj); - } catch (IllegalAccessException | InvocationTargetException e) { - BotLogger.error("Could not add ability extension", TAG, e); - throw new RuntimeException(e); - } - }; - } - - /** - * Invokes the method and retrieves its return {@link Ability}. - * - * @param obj an bot or extension that this method is invoked with - * @return a {@link Function} which returns the {@link Ability} returned by the given method - */ - private Function returnAbility(Object obj) { - return method -> { - try { - return (Ability) method.invoke(obj); - } catch (IllegalAccessException | InvocationTargetException e) { - BotLogger.error("Could not add ability", TAG, e); - throw new RuntimeException(e); - } - }; - } - - /** - * Invokes the method and retrieves its return {@link Reply}. - * - * @param obj an bot or extension that this method is invoked with - * @return a {@link Function} which returns the {@link Reply} returned by the given method - */ - private Function returnReply(Object obj) { - return method -> { - try { - return (Reply) method.invoke(obj); - } catch (IllegalAccessException | InvocationTargetException e) { - BotLogger.error("Could not add reply", TAG, e); - throw new RuntimeException(e); - } - }; - } - - private void postConsumption(Pair pair) { - ofNullable(pair.b().postAction()) - .ifPresent(consumer -> consumer.accept(pair.a())); - } - - Pair consumeUpdate(Pair pair) { - pair.b().action().accept(pair.a()); - return pair; - } - - Pair getContext(Trio trio) { - Update update = trio.a(); - User user = AbilityUtils.getUser(update); - - return Pair.of(newContext(update, user, getChatId(update), trio.c()), trio.b()); - } - - boolean checkBlacklist(Update update) { - Integer id = AbilityUtils.getUser(update).getId(); - - return id == creatorId() || !blacklist().contains(id); - } - - boolean checkInput(Trio trio) { - String[] tokens = trio.c(); - int abilityTokens = trio.b().tokens(); - - boolean isOk = abilityTokens == 0 || (tokens.length > 0 && tokens.length == abilityTokens); - - if (!isOk) - silent.send( - getLocalizedMessage( - CHECK_INPUT_FAIL, - AbilityUtils.getUser(trio.a()).getLanguageCode(), - abilityTokens, abilityTokens == 1 ? "input" : "inputs"), - getChatId(trio.a())); - return isOk; - } - - boolean checkLocality(Trio trio) { - Update update = trio.a(); - Locality locality = isUserMessage(update) ? USER : GROUP; - Locality abilityLocality = trio.b().locality(); - - boolean isOk = abilityLocality == ALL || locality == abilityLocality; - - if (!isOk) - silent.send( - getLocalizedMessage( - CHECK_LOCALITY_FAIL, - AbilityUtils.getUser(trio.a()).getLanguageCode(), - abilityLocality.toString().toLowerCase()), - getChatId(trio.a())); - return isOk; - } - - boolean checkPrivacy(Trio trio) { - Update update = trio.a(); - User user = AbilityUtils.getUser(update); - Privacy privacy; - int id = user.getId(); - - privacy = getPrivacy(update, id); - - boolean isOk = privacy.compareTo(trio.b().privacy()) >= 0; - - if (!isOk) - silent.send( - getLocalizedMessage( - CHECK_PRIVACY_FAIL, - AbilityUtils.getUser(trio.a()).getLanguageCode()), - getChatId(trio.a())); - return isOk; - } - - @NotNull - private Privacy getPrivacy(Update update, int id) { - return isCreator(id) ? - CREATOR : isAdmin(id) ? - ADMIN : (isGroupUpdate(update) || isSuperGroupUpdate(update)) && isGroupAdmin(update, id) ? - GROUP_ADMIN : PUBLIC; - } - - private boolean isGroupAdmin(Update update, int id) { - GetChatAdministrators admins = new GetChatAdministrators().setChatId(getChatId(update)); - - return silent.execute(admins) - .orElse(new ArrayList<>()).stream() - .anyMatch(member -> member.getUser().getId() == id); - } - - private boolean isCreator(int id) { - return id == creatorId(); - } - - private boolean isAdmin(Integer id) { - return admins().contains(id); - } - - boolean validateAbility(Trio trio) { - return trio.b() != null; - } - - Trio getAbility(Update update) { - // Handle updates without messages - // Passing through this function means that the global flags have passed - Message msg = update.getMessage(); - if (!update.hasMessage() || !msg.hasText()) - return Trio.of(update, abilities.get(DEFAULT), new String[]{}); - - String[] tokens = msg.getText().split(" "); - - if (tokens[0].startsWith("/")) { - String abilityToken = stripBotUsername(tokens[0].substring(1)).toLowerCase(); - Ability ability = abilities.get(abilityToken); - tokens = Arrays.copyOfRange(tokens, 1, tokens.length); - return Trio.of(update, ability, tokens); - } else { - Ability ability = abilities.get(DEFAULT); - return Trio.of(update, ability, tokens); - } - } - - private String stripBotUsername(String token) { - return compile(format("@%s", botUsername), CASE_INSENSITIVE) - .matcher(token) - .replaceAll(""); - } - - Update addUser(Update update) { - User endUser = AbilityUtils.getUser(update); - - users().compute(endUser.getId(), (id, user) -> { - if (user == null) { - updateUserId(user, endUser); - return endUser; - } - - if (!user.equals(endUser)) { - updateUserId(user, endUser); - return endUser; - } - - return user; - }); - - db.commit(); - return update; - } - - private void updateUserId(User oldUser, User newUser) { - if (oldUser != null && oldUser.getUserName() != null) { - // Remove old username -> ID - userIds().remove(oldUser.getUserName()); + }; } - if (newUser.getUserName() != null) { - // Add new mapping with the new username - userIds().put(newUser.getUserName().toLowerCase(), newUser.getId()); + /** + * Invokes the method and retrieves its return {@link Ability}. + * + * @param obj an bot or extension that this method is invoked with + * @return a {@link Function} which returns the {@link Ability} returned by the given method + */ + private Function returnAbility(Object obj) { + return method -> { + try { + return (Ability) method.invoke(obj); + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("Could not add ability", e); + throw new RuntimeException(e); + } + }; } - } - boolean filterReply(Update update) { - return replies.stream() - .filter(reply -> reply.isOkFor(update)) - .map(reply -> { - reply.actOn(update); - return false; - }) - .reduce(true, Boolean::logicalAnd); - } + /** + * Invokes the method and retrieves its return {@link Reply}. + * + * @param obj an bot or extension that this method is invoked with + * @return a {@link Function} which returns the {@link Reply} returned by the given method + */ + private Function returnReply(Object obj) { + return method -> { + try { + return (Reply) method.invoke(obj); + } catch (IllegalAccessException | InvocationTargetException e) { + log.error("Could not add reply", e); + throw new RuntimeException(e); + } + }; + } - boolean checkMessageFlags(Trio trio) { - Ability ability = trio.b(); - Update update = trio.a(); + private void postConsumption(Pair pair) { + ofNullable(pair.b().postAction()) + .ifPresent(consumer -> consumer.accept(pair.a())); + } - // The following variable is required to avoid bug #JDK-8044546 - BiFunction, Boolean> flagAnd = (flag, nextFlag) -> flag && nextFlag.test(update); - return ability.flags().stream() - .reduce(true, flagAnd, Boolean::logicalAnd); - } + Pair consumeUpdate(Pair pair) { + pair.b().action().accept(pair.a()); + return pair; + } - private File downloadFileWithId(String fileId) throws TelegramApiException { - return sender.downloadFile(sender.execute(new GetFile().setFileId(fileId))); - } + Pair getContext(Trio trio) { + Update update = trio.a(); + User user = AbilityUtils.getUser(update); + + return Pair.of(newContext(update, user, getChatId(update), trio.c()), trio.b()); + } + + boolean checkBlacklist(Update update) { + Integer id = AbilityUtils.getUser(update).getId(); + + return id == creatorId() || !blacklist().contains(id); + } + + boolean checkInput(Trio trio) { + String[] tokens = trio.c(); + int abilityTokens = trio.b().tokens(); + + boolean isOk = abilityTokens == 0 || (tokens.length > 0 && tokens.length == abilityTokens); + + if (!isOk) + silent.send( + getLocalizedMessage( + CHECK_INPUT_FAIL, + AbilityUtils.getUser(trio.a()).getLanguageCode(), + abilityTokens, abilityTokens == 1 ? "input" : "inputs"), + getChatId(trio.a())); + return isOk; + } + + boolean checkLocality(Trio trio) { + Update update = trio.a(); + Locality locality = isUserMessage(update) ? USER : GROUP; + Locality abilityLocality = trio.b().locality(); + + boolean isOk = abilityLocality == ALL || locality == abilityLocality; + + if (!isOk) + silent.send( + getLocalizedMessage( + CHECK_LOCALITY_FAIL, + AbilityUtils.getUser(trio.a()).getLanguageCode(), + abilityLocality.toString().toLowerCase()), + getChatId(trio.a())); + return isOk; + } + + boolean checkPrivacy(Trio trio) { + Update update = trio.a(); + User user = AbilityUtils.getUser(update); + Privacy privacy; + int id = user.getId(); + + privacy = getPrivacy(update, id); + + boolean isOk = privacy.compareTo(trio.b().privacy()) >= 0; + + if (!isOk) + silent.send( + getLocalizedMessage( + CHECK_PRIVACY_FAIL, + AbilityUtils.getUser(trio.a()).getLanguageCode()), + getChatId(trio.a())); + return isOk; + } + + @NotNull + private Privacy getPrivacy(Update update, int id) { + return isCreator(id) ? + CREATOR : isAdmin(id) ? + ADMIN : (isGroupUpdate(update) || isSuperGroupUpdate(update)) && isGroupAdmin(update, id) ? + GROUP_ADMIN : PUBLIC; + } + + private boolean isGroupAdmin(Update update, int id) { + GetChatAdministrators admins = new GetChatAdministrators().setChatId(getChatId(update)); + + return silent.execute(admins) + .orElse(new ArrayList<>()).stream() + .anyMatch(member -> member.getUser().getId() == id); + } + + private boolean isCreator(int id) { + return id == creatorId(); + } + + private boolean isAdmin(Integer id) { + return admins().contains(id); + } + + boolean validateAbility(Trio trio) { + return trio.b() != null; + } + + Trio getAbility(Update update) { + // Handle updates without messages + // Passing through this function means that the global flags have passed + Message msg = update.getMessage(); + if (!update.hasMessage() || !msg.hasText()) + return Trio.of(update, abilities.get(DEFAULT), new String[]{}); + + String[] tokens = msg.getText().split(" "); + + if (tokens[0].startsWith("/")) { + String abilityToken = stripBotUsername(tokens[0].substring(1)).toLowerCase(); + Ability ability = abilities.get(abilityToken); + tokens = Arrays.copyOfRange(tokens, 1, tokens.length); + return Trio.of(update, ability, tokens); + } else { + Ability ability = abilities.get(DEFAULT); + return Trio.of(update, ability, tokens); + } + } + + private String stripBotUsername(String token) { + return compile(format("@%s", botUsername), CASE_INSENSITIVE) + .matcher(token) + .replaceAll(""); + } + + Update addUser(Update update) { + User endUser = AbilityUtils.getUser(update); + + users().compute(endUser.getId(), (id, user) -> { + if (user == null) { + updateUserId(user, endUser); + return endUser; + } + + if (!user.equals(endUser)) { + updateUserId(user, endUser); + return endUser; + } + + return user; + }); + + db.commit(); + return update; + } + + private void updateUserId(User oldUser, User newUser) { + if (oldUser != null && oldUser.getUserName() != null) { + // Remove old username -> ID + userIds().remove(oldUser.getUserName()); + } + + if (newUser.getUserName() != null) { + // Add new mapping with the new username + userIds().put(newUser.getUserName().toLowerCase(), newUser.getId()); + } + } + + boolean filterReply(Update update) { + return replies.stream() + .filter(reply -> reply.isOkFor(update)) + .map(reply -> { + reply.actOn(update); + return false; + }) + .reduce(true, Boolean::logicalAnd); + } + + boolean checkMessageFlags(Trio trio) { + Ability ability = trio.b(); + Update update = trio.a(); + + // The following variable is required to avoid bug #JDK-8044546 + BiFunction, Boolean> flagAnd = (flag, nextFlag) -> flag && nextFlag.test(update); + return ability.flags().stream() + .reduce(true, flagAnd, Boolean::logicalAnd); + } + + private File downloadFileWithId(String fileId) throws TelegramApiException { + return sender.downloadFile(sender.execute(new GetFile().setFileId(fileId))); + } - private String escape(String username) { - return username.replace("_", "\\_"); - } + private String escape(String username) { + return username.replace("_", "\\_"); + } } \ No newline at end of file diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java index 9fce2c8a..bb33ec22 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java @@ -11,7 +11,7 @@ import java.util.Set; /** * This interface represents the high-level methods exposed to the user when handling an {@link Update}. * Example usage: - *

Ability.builder().action(ctx -> {db.getSet(USERS); doSomething();})*

+ *

Ability.builder().action(ctx -> {db.getSet(USERS); doSomething();})

* {@link BaseAbilityBot} contains a handle on the db that the user can use inside his declared abilities. * * @author Abbas Abou Daya diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBContext.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBContext.java index 3af7fa8b..a6977917 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBContext.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBContext.java @@ -3,12 +3,13 @@ package org.telegram.abilitybots.api.db; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.mapdb.Atomic; import org.mapdb.DB; import org.mapdb.DBMaker; import org.mapdb.Serializer; import org.telegram.abilitybots.api.util.Pair; -import org.telegram.telegrambots.meta.logging.BotLogger; import java.io.IOException; import java.util.Collection; @@ -34,7 +35,7 @@ import static org.mapdb.Serializer.JAVA; */ @SuppressWarnings({"unchecked", "WeakerAccess"}) public class MapDBContext implements DBContext { - private static final String TAG = DBContext.class.getSimpleName(); + private static final Logger log = LogManager.getLogger(MapDBContext.class); private final DB db; private final ObjectMapper objectMapper; @@ -125,7 +126,7 @@ public class MapDBContext implements DBContext { doRecover(backupData); return true; } catch (IOException e) { - BotLogger.error(format("Could not recover DB data from file with String representation %s", backup), TAG, e); + log.error(format("Could not recover DB data from file with String representation %s", backup), e); // Attempt to fallback to data snapshot before recovery doRecover(snapshot); return false; @@ -206,7 +207,7 @@ public class MapDBContext implements DBContext { List entryList = (List) value; getList(name).addAll(entryList); } else { - BotLogger.error(TAG, format("Unable to identify object type during DB recovery, entry name: %s", name)); + log.error(format("Unable to identify object type during DB recovery, entry name: %s", name)); } }); commit(); @@ -216,7 +217,7 @@ public class MapDBContext implements DBContext { try { return objectMapper.writeValueAsString(obj); } catch (JsonProcessingException e) { - BotLogger.info(format("Failed to read the JSON representation of object: %s", obj), TAG, e); + log.info(format("Failed to read the JSON representation of object: %s", obj), e); return "Error reading required data..."; } } diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Ability.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Ability.java index 75259dc3..c3004c8b 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Ability.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Ability.java @@ -2,8 +2,9 @@ package org.telegram.abilitybots.api.objects; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.telegram.telegrambots.meta.api.objects.Update; -import org.telegram.telegrambots.meta.logging.BotLogger; import java.util.Arrays; import java.util.List; @@ -34,7 +35,7 @@ import static org.apache.commons.lang3.StringUtils.*; * @author Abbas Abou Daya */ public final class Ability { - private static final String TAG = Ability.class.getSimpleName(); + private static final Logger log = LogManager.getLogger(Ability.class); private final String name; private final String info; @@ -63,7 +64,7 @@ public final class Ability { this.action = checkNotNull(action, "Method action can't be empty. Please assign a function by using .action() method"); if (postAction == null) - BotLogger.info(TAG, format("No post action was detected for method with name [%s]", name)); + log.info(format("No post action was detected for method with name [%s]", name)); this.flags = ofNullable(flags).map(Arrays::asList).orElse(newArrayList()); diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/sender/SilentSender.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/sender/SilentSender.java index a6cb37f9..b5d48bfb 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/sender/SilentSender.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/sender/SilentSender.java @@ -1,11 +1,12 @@ package org.telegram.abilitybots.api.sender; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.telegram.telegrambots.meta.api.methods.BotApiMethod; import org.telegram.telegrambots.meta.api.methods.send.SendMessage; import org.telegram.telegrambots.meta.api.objects.Message; import org.telegram.telegrambots.meta.api.objects.replykeyboard.ForceReplyKeyboard; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; -import org.telegram.telegrambots.meta.logging.BotLogger; import java.io.Serializable; import java.util.Optional; @@ -17,7 +18,7 @@ import java.util.Optional; * @author Abbas Abou Daya */ public class SilentSender { - private static final String TAG = SilentSender.class.getSimpleName(); + private static final Logger log = LogManager.getLogger(SilentSender.class); private final MessageSender sender; @@ -46,7 +47,7 @@ public class SilentSender { try { return Optional.ofNullable(sender.execute(method)); } catch (TelegramApiException e) { - BotLogger.error("Could not execute bot API method", TAG, e); + log.error("Could not execute bot API method", e); return Optional.empty(); } } @@ -55,7 +56,7 @@ public class SilentSender { try { return Optional.ofNullable(sender.execute(method)); } catch (TelegramApiException e) { - BotLogger.error("Could not execute bot API method", TAG, e); + log.error("Could not execute bot API method", e); return Optional.empty(); } } diff --git a/telegrambots-extensions/src/main/java/org/telegram/telegrambots/extensions/bots/timedbot/TimedSendLongPollingBot.java b/telegrambots-extensions/src/main/java/org/telegram/telegrambots/extensions/bots/timedbot/TimedSendLongPollingBot.java index 7de54f7e..00a240a3 100644 --- a/telegrambots-extensions/src/main/java/org/telegram/telegrambots/extensions/bots/timedbot/TimedSendLongPollingBot.java +++ b/telegrambots-extensions/src/main/java/org/telegram/telegrambots/extensions/bots/timedbot/TimedSendLongPollingBot.java @@ -11,9 +11,9 @@ import java.util.concurrent.atomic.AtomicBoolean; * Created by Daniil Nikanov aka JetCoder */ +@SuppressWarnings("unused") public abstract class TimedSendLongPollingBot extends TelegramLongPollingBot { - private static final long MANY_CHATS_SEND_INTERVAL = 33; private static final long ONE_CHAT_SEND_INTERVAL = 1000; private static final long CHAT_INACTIVE_INTERVAL = 1000 * 60 * 10; private final Timer mSendTimer = new Timer(true); @@ -21,88 +21,29 @@ public abstract class TimedSendLongPollingBot extends TelegramLongPollingBot private final ArrayList mSendQueues = new ArrayList<>(); private final AtomicBoolean mSendRequested = new AtomicBoolean(false); - private final class MessageSenderTask extends TimerTask - { - @Override - public void run() - { - //mSendRequested used for optimisation to not traverse all mMessagesMap 30 times per second all the time - if (!mSendRequested.getAndSet(false)) - return; - - long currentTime = System.currentTimeMillis(); - mSendQueues.clear(); - boolean processNext = false; - - //First step - find all chats in which already allowed to send message (passed more than 1000 ms from previuos send) - Iterator> it = mMessagesMap.entrySet().iterator(); - while (it.hasNext()) - { - MessageQueue queue = it.next().getValue(); - int state = queue.getCurrentState(currentTime); //Actual check here - if (state == MessageQueue.GET_MESSAGE) - { - mSendQueues.add(queue); - processNext = true; - } - else if (state == MessageQueue.WAIT) - { - processNext = true; - } - else if (state == MessageQueue.DELETE) - { - it.remove(); - } - } - - //If any of chats are in state WAIT or GET_MESSAGE, request another iteration - if (processNext) - mSendRequested.set(true); - - //Second step - find oldest waiting queue and peek it's message - MessageQueue sendQueue = null; - long oldestPutTime = Long.MAX_VALUE; - for (int i = 0; i < mSendQueues.size(); i++) - { - MessageQueue queue = mSendQueues.get(i); - long putTime = queue.getPutTime(); - if (putTime < oldestPutTime) - { - oldestPutTime = putTime; - sendQueue = queue; - } - } - if (sendQueue == null) //Possible if on first step wasn't found any chats in state GET_MESSAGE - return; - - //Invoke the send callback. ChatId is passed for possible additional processing - sendMessageCallback(sendQueue.getChatId(), sendQueue.getMessage(currentTime)); - } - } - private static class MessageQueue { - public static final int EMPTY = 0; //Queue is empty - public static final int WAIT = 1; //Queue has message(s) but not yet allowed to send - public static final int DELETE = 2; //No one message of given queue was sent longer than CHAT_INACTIVE_INTERVAL, delete for optimisation - public static final int GET_MESSAGE = 3; //Queue has message(s) and ready to send + static final int EMPTY = 0; //Queue is empty + static final int WAIT = 1; //Queue has message(s) but not yet allowed to send + static final int DELETE = 2; //No one message of given queue was sent longer than CHAT_INACTIVE_INTERVAL, delete for optimisation + static final int GET_MESSAGE = 3; //Queue has message(s) and ready to send private final ConcurrentLinkedQueue mQueue = new ConcurrentLinkedQueue<>(); private final Long mChatId; private long mLastSendTime; //Time of last peek from queue private volatile long mLastPutTime; //Time of last put into queue - public MessageQueue(Long chatId) + MessageQueue(Long chatId) { mChatId = chatId; } - public synchronized void putMessage(Object msg) + synchronized void putMessage(Object msg) { mQueue.add(msg); mLastPutTime = System.currentTimeMillis(); } - public synchronized int getCurrentState(long currentTime) + synchronized int getCurrentState(long currentTime) { //currentTime is passed as parameter for optimisation to do not recall currentTimeMillis() many times long interval = currentTime - mLastSendTime; @@ -117,29 +58,23 @@ public abstract class TimedSendLongPollingBot extends TelegramLongPollingBot return WAIT; } - public synchronized Object getMessage(long currentTime) + synchronized Object getMessage(long currentTime) { mLastSendTime = currentTime; return mQueue.poll(); } - public long getPutTime() + long getPutTime() { return mLastPutTime; } - public Long getChatId() + Long getChatId() { return mChatId; } } - //Constructor - protected TimedSendLongPollingBot() - { - mSendTimer.schedule(new MessageSenderTask(), MANY_CHATS_SEND_INTERVAL, MANY_CHATS_SEND_INTERVAL); - } - //Something like destructor public void finish() { @@ -214,5 +149,5 @@ public abstract class TimedSendLongPollingBot extends TelegramLongPollingBot } } */ - public abstract void sendMessageCallback(Long chatId, Object messageRequest); + abstract void sendMessageCallback(Long chatId, Object messageRequest); } diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/ApiContext.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/ApiContext.java index fe783e65..a5a2e0ce 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/ApiContext.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/ApiContext.java @@ -4,8 +4,8 @@ import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Singleton; - -import org.telegram.telegrambots.meta.logging.BotLogger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.text.MessageFormat; import java.util.HashMap; @@ -16,6 +16,8 @@ import java.util.Map; * @version 1.0 */ public class ApiContext { + private static final Logger log = LogManager.getLogger(ApiContext.class); + private static final Object lock = new Object(); private static Injector INJECTOR; private static Map bindings = new HashMap<>(); @@ -27,14 +29,14 @@ public class ApiContext { public static void register(Class type, Class implementation) { if (bindings.containsKey(type)) { - BotLogger.debug("ApiContext", MessageFormat.format("Class {0} already registered", type.getName())); + log.debug(MessageFormat.format("Class {0} already registered", type.getName())); } bindings.put(type, implementation); } public static void registerSingleton(Class type, Class implementation) { if (singletonBindings.containsKey(type)) { - BotLogger.debug("ApiContext", MessageFormat.format("Class {0} already registered", type.getName())); + log.debug(MessageFormat.format("Class {0} already registered", type.getName())); } singletonBindings.put(type, implementation); } diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/inlinequery/result/chached/package-info.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/inlinequery/result/chached/package-info.java deleted file mode 100644 index 96d11b44..00000000 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/inlinequery/result/chached/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * @deprecated Replaced by {@link org.telegram.telegrambots.meta.api.objects.inlinequery.result.cached} - */ -package org.telegram.telegrambots.meta.api.objects.inlinequery.result.chached; \ No newline at end of file diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/exceptions/TelegramApiRequestException.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/exceptions/TelegramApiRequestException.java index 4d39bcfd..01d52b38 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/exceptions/TelegramApiRequestException.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/exceptions/TelegramApiRequestException.java @@ -18,11 +18,11 @@ package org.telegram.telegrambots.meta.exceptions; import com.fasterxml.jackson.databind.ObjectMapper; - +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.json.JSONObject; -import org.telegram.telegrambots.meta.api.objects.ResponseParameters; import org.telegram.telegrambots.meta.api.objects.ApiResponse; -import org.telegram.telegrambots.meta.logging.BotLogger; +import org.telegram.telegrambots.meta.api.objects.ResponseParameters; import java.io.IOException; @@ -33,6 +33,8 @@ import java.io.IOException; * Exception thrown when something goes wrong in the api */ public class TelegramApiRequestException extends TelegramApiException { + private static final Logger log = LogManager.getLogger(TelegramApiRequestException.class); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final String ERRORDESCRIPTIONFIELD = "description"; private static final String ERRORCODEFIELD = "error_code"; @@ -54,7 +56,7 @@ public class TelegramApiRequestException extends TelegramApiException { try { parameters = OBJECT_MAPPER.readValue(object.getJSONObject(PARAMETERSFIELD).toString(), ResponseParameters.class); } catch (IOException e) { - BotLogger.severe("APIEXCEPTION", e); + log.fatal(e.getLocalizedMessage(), e); } } } diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/BotLogger.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/BotLogger.java deleted file mode 100644 index cb8b3bce..00000000 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/BotLogger.java +++ /dev/null @@ -1,171 +0,0 @@ -package org.telegram.telegrambots.meta.logging; - -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * @author Ruben Bermudez - * @version 2.0 - * @brief Logger to file - * @date 21/01/15 - */ -public class BotLogger { - private static final Logger logger = Logger.getLogger("Telegram Bots Api"); - - public static void setLevel(Level level) { - logger.setLevel(level); - } - - public static Level getLevel() { - return logger.getLevel(); - } - - public static void registerLogger(Handler handler) { - logger.addHandler(handler); - } - - public static void log(Level level, String tag, String msg) { - logger.log(level, String.format("%s - %s", tag, msg)); - } - - public static void severe(String tag, String msg) { - logger.severe(String.format("%s - %s", tag, msg)); - } - - public static void warn(String tag, String msg) { - warning(tag, msg); - } - - public static void debug(String tag, String msg) { - fine(tag, msg); - } - - public static void error(String tag, String msg) { - severe(tag, msg); - } - - public static void trace(String tag, String msg) { - finer(tag, msg); - } - - public static void warning(String tag, String msg) { - logger.warning(String.format("%s - %s", tag, msg)); - } - - public static void info(String tag, String msg) { - logger.info(String.format("%s - %s", tag, msg)); - } - - public static void config(String tag, String msg) { - logger.config(String.format("%s - %s", tag, msg)); - } - - public static void fine(String tag, String msg) { - logger.fine(String.format("%s - %s", tag, msg)); - } - - public static void finer(String tag, String msg) { - logger.finer(String.format("%s - %s", tag, msg)); - } - - public static void finest(String tag, String msg) { - logger.finest(String.format("%s - %s", tag, msg)); - } - - public static void log(Level level, String tag, Throwable throwable) { - logger.log(level, tag, throwable); - } - - public static void log(Level level, String tag, String msg, Throwable thrown) { - logger.log(level, String.format("%s - %s", tag, msg), thrown); - } - - public static void severe(String tag, Throwable throwable) { - logger.log(Level.SEVERE, tag, throwable); - } - - public static void warning(String tag, Throwable throwable) { - logger.log(Level.WARNING, tag, throwable); - } - - public static void info(String tag, Throwable throwable) { - logger.log(Level.INFO, tag, throwable); - } - - public static void config(String tag, Throwable throwable) { - logger.log(Level.CONFIG, tag, throwable); - } - - public static void fine(String tag, Throwable throwable) { - logger.log(Level.FINE, tag, throwable); - } - - public static void finer(String tag, Throwable throwable) { - logger.log(Level.FINER, tag, throwable); - } - - public static void finest(String tag, Throwable throwable) { - logger.log(Level.FINEST, tag, throwable); - } - - public static void warn(String tag, Throwable throwable) { - warning(tag, throwable); - } - - public static void debug(String tag, Throwable throwable) { - fine(tag, throwable); - } - - public static void error(String tag, Throwable throwable) { - severe(tag, throwable); - } - - public static void trace(String tag, Throwable throwable) { - finer(tag, throwable); - } - - public static void severe(String msg, String tag, Throwable throwable) { - log(Level.SEVERE, tag, msg, throwable); - } - - public static void warning(String msg, String tag, Throwable throwable) { - log(Level.WARNING, tag, msg, throwable); - } - - public static void info(String msg, String tag, Throwable throwable) { - log(Level.INFO, tag, msg, throwable); - } - - public static void config(String msg, String tag, Throwable throwable) { - log(Level.CONFIG, tag, msg, throwable); - } - - public static void fine(String msg, String tag, Throwable throwable) { - log(Level.FINE, tag, msg, throwable); - } - - public static void finer(String msg, String tag, Throwable throwable) { - log(Level.FINER, tag, msg, throwable); - } - - public static void finest(String msg, String tag, Throwable throwable) { - log(Level.FINEST, tag, msg, throwable); - } - - public static void warn(String msg, String tag, Throwable throwable) { - log(Level.WARNING, tag, msg, throwable); - } - - public static void debug(String msg, String tag, Throwable throwable) { - log(Level.FINE, tag, msg, throwable); - } - - public static void error(String msg, String tag, Throwable throwable) { - log(Level.SEVERE, tag, msg, throwable); - } - - public static void trace(String msg, String tag, Throwable throwable) { - log(Level.FINER, tag, msg, throwable); - } -} diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/BotsFileHandler.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/BotsFileHandler.java deleted file mode 100644 index 0da14c3f..00000000 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/BotsFileHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.telegram.telegrambots.meta.logging; - -import java.io.IOException; -import java.util.logging.FileHandler; - -/** - * @author Ruben Bermudez - * @version 1.0 - * @brief Handler to use a file as logs destination with {@link BotLogger} - * @date 19 of May of 2016 - */ -public class BotsFileHandler extends FileHandler { - private static final String filePattern = "./TelegramBots%g.%u.log"; - - public BotsFileHandler() throws IOException, SecurityException { - super(filePattern, 1024 * 1024 * 10, 50, true); - setFormatter(new FileFormatter()); - } - - public BotsFileHandler(int limit, int count) throws IOException, SecurityException { - super(filePattern, limit, count); - setFormatter(new FileFormatter()); - } - - public BotsFileHandler(String pattern) throws IOException, SecurityException { - super(pattern); - setFormatter(new FileFormatter()); - } - - public BotsFileHandler(String pattern, boolean append) throws IOException, SecurityException { - super(pattern, append); - setFormatter(new FileFormatter()); - } - - public BotsFileHandler(String pattern, int limit, int count) throws IOException, SecurityException { - super(pattern, limit, count); - setFormatter(new FileFormatter()); - } - - public BotsFileHandler(String pattern, int limit, int count, boolean append) throws IOException, SecurityException { - super(pattern, limit, count, append); - setFormatter(new FileFormatter()); - } - -} diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/FileFormatter.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/FileFormatter.java deleted file mode 100644 index ca6a1e4b..00000000 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/logging/FileFormatter.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.telegram.telegrambots.meta.logging; - -import java.time.LocalDateTime; -import java.util.logging.Formatter; -import java.util.logging.Level; -import java.util.logging.LogRecord; - -/** - * @author Ruben Bermudez - * @version 1.0 - * @brief Formatter for {@link BotsFileHandler} - * @date 19 of May of 2016 - */ -class FileFormatter extends Formatter { - - @Override - public String format(LogRecord record) { - final LocalDateTime currentDate = LocalDateTime.now(); - final String dateForLog = dateFormatterForLogs(currentDate); - String result; - if (record.getThrown() == null) { - result = logMsgToFile(record.getLevel(), record.getMessage(), dateForLog); - } else { - result = logThrowableToFile(record.getLevel(), record.getMessage(), record.getThrown(), dateForLog); - } - return result; - } - - private static String dateFormatterForLogs(LocalDateTime dateTime) { - String dateString = "["; - dateString += dateTime.getDayOfMonth() + "_"; - dateString += dateTime.getMonthValue() + "_"; - dateString += dateTime.getYear() + "_"; - dateString += dateTime.getHour() + ":"; - dateString += dateTime.getMinute() + ":"; - dateString += dateTime.getSecond(); - dateString += "] "; - return dateString; - } - - private static String logMsgToFile(Level level, String msg, String dateForLog) { - return String.format("%s{%s} %s\n", dateForLog, level.toString(), msg); - } - - private static String logThrowableToFile(Level level, String message, Throwable throwable, String dateForLog) { - String throwableLog = String.format("%s{%s} %s - %s\n", dateForLog, level.toString(), message, throwable.toString()); - for (StackTraceElement element : throwable.getStackTrace()) { - throwableLog += "\tat " + element + "\n"; - } - return throwableLog; - } -} diff --git a/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotInitializer.java b/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotInitializer.java index 241d962d..951dc3b8 100644 --- a/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotInitializer.java +++ b/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotInitializer.java @@ -1,12 +1,13 @@ package org.telegram.telegrambots.starter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.InitializingBean; import org.telegram.telegrambots.meta.TelegramBotsApi; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; import org.telegram.telegrambots.meta.generics.BotSession; import org.telegram.telegrambots.meta.generics.LongPollingBot; import org.telegram.telegrambots.meta.generics.WebhookBot; -import org.telegram.telegrambots.meta.logging.BotLogger; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -20,6 +21,7 @@ import static java.lang.String.format; * Receives all beand which are #LongPollingBot and #WebhookBot and register them in #TelegramBotsApi. */ public class TelegramBotInitializer implements InitializingBean { + private static final Logger log = LogManager.getLogger(TelegramBotInitializer.class); private final TelegramBotsApi telegramBotsApi; private final List longPollingBots; @@ -37,7 +39,7 @@ public class TelegramBotInitializer implements InitializingBean { } @Override - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { try { for (LongPollingBot bot : longPollingBots) { BotSession session = telegramBotsApi.registerBot(bot); @@ -54,12 +56,8 @@ public class TelegramBotInitializer implements InitializingBean { private void handleAnnotatedMethod(Object bot, Method method, BotSession session) { try { if (method.getParameterCount() > 1) { - BotLogger.warn(this.getClass().getSimpleName(), - format("Method %s of Type %s has too many parameters", - method.getName(), - method.getDeclaringClass().getCanonicalName() - ) - ); + log.warn(format("Method %s of Type %s has too many parameters", + method.getName(), method.getDeclaringClass().getCanonicalName())); return; } if (method.getParameterCount() == 0) { @@ -70,18 +68,11 @@ public class TelegramBotInitializer implements InitializingBean { method.invoke(bot, session); return; } - BotLogger.warn(this.getClass().getSimpleName(), - format("Method %s of Type %s has invalid parameter type", - method.getName(), - method.getDeclaringClass().getCanonicalName() - ) - ); + log.warn(format("Method %s of Type %s has invalid parameter type", + method.getName(), method.getDeclaringClass().getCanonicalName())); } catch (InvocationTargetException | IllegalAccessException e) { - BotLogger.error(this.getClass().getSimpleName(), - format("Couldn't invoke Method %s of Type %s", - method.getName(), method.getDeclaringClass().getCanonicalName() - ) - ); + log.error(format("Couldn't invoke Method %s of Type %s", + method.getName(), method.getDeclaringClass().getCanonicalName())); } } diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultBotSession.java b/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultBotSession.java index 0c1c9f13..319e39ac 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultBotSession.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultBotSession.java @@ -11,6 +11,8 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.json.JSONException; import org.telegram.telegrambots.meta.ApiConstants; import org.telegram.telegrambots.meta.api.methods.updates.GetUpdates; @@ -19,7 +21,6 @@ import org.telegram.telegrambots.bots.DefaultBotOptions; import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException; import org.telegram.telegrambots.facilities.TelegramHttpClientBuilder; import org.telegram.telegrambots.meta.generics.*; -import org.telegram.telegrambots.meta.logging.BotLogger; import java.io.IOException; import java.io.InvalidObjectException; @@ -39,7 +40,7 @@ import static org.telegram.telegrambots.Constants.SOCKET_TIMEOUT; * Thread to request updates with active wait */ public class DefaultBotSession implements BotSession { - private static final String LOGTAG = "BOTSESSION"; + private static final Logger log = LogManager.getLogger(DefaultBotSession.class); private AtomicBoolean running = new AtomicBoolean(false); @@ -170,7 +171,7 @@ public class DefaultBotSession implements BotSession { try { httpclient.close(); } catch (IOException e) { - BotLogger.warn(LOGTAG, e); + log.warn(e.getLocalizedMessage(), e); } } super.interrupt(); @@ -203,10 +204,10 @@ public class DefaultBotSession implements BotSession { if (!running.get()) { receivedUpdates.clear(); } - BotLogger.debug(LOGTAG, e); + log.debug(e.getLocalizedMessage(), e); interrupt(); } catch (Exception global) { - BotLogger.severe(LOGTAG, global); + log.fatal(global.getLocalizedMessage(), global); try { synchronized (lock) { lock.wait(exponentialBackOff.nextBackOffMillis()); @@ -215,14 +216,14 @@ public class DefaultBotSession implements BotSession { if (!running.get()) { receivedUpdates.clear(); } - BotLogger.debug(LOGTAG, e); + log.debug(e.getLocalizedMessage(), e); interrupt(); } } } } } - BotLogger.debug(LOGTAG, "Reader thread has being closed"); + log.debug("Reader thread has being closed"); } private List getUpdatesFromServer() throws IOException { @@ -248,7 +249,7 @@ public class DefaultBotSession implements BotSession { String responseContent = EntityUtils.toString(buf, StandardCharsets.UTF_8); if (response.getStatusLine().getStatusCode() >= 500) { - BotLogger.warn(LOGTAG, responseContent); + log.warn(responseContent); synchronized (lock) { lock.wait(500); } @@ -258,15 +259,15 @@ public class DefaultBotSession implements BotSession { exponentialBackOff.reset(); return updates; } catch (JSONException e) { - BotLogger.severe(responseContent, LOGTAG, e); + log.error("Error deserializing update: " + responseContent, e); } } } catch (SocketException | InvalidObjectException | TelegramApiRequestException e) { - BotLogger.severe(LOGTAG, e); + log.fatal(e.getLocalizedMessage(), e); } catch (SocketTimeoutException e) { - BotLogger.fine(LOGTAG, e); + log.info(e.getLocalizedMessage(), e); } catch (InterruptedException e) { - BotLogger.fine(LOGTAG, e); + log.info(e.getLocalizedMessage(), e); interrupt(); } return Collections.emptyList(); @@ -305,13 +306,13 @@ public class DefaultBotSession implements BotSession { } callback.onUpdatesReceived(updates); } catch (InterruptedException e) { - BotLogger.debug(LOGTAG, e); + log.debug(e.getLocalizedMessage(), e); interrupt(); } catch (Exception e) { - BotLogger.severe(LOGTAG, e); + log.fatal(e.getLocalizedMessage(), e); } } - BotLogger.debug(LOGTAG, "Handler thread has being closed"); + log.debug("Handler thread has being closed"); } } } diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/ExponentialBackOff.java b/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/ExponentialBackOff.java index 2fbf096c..80769b29 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/ExponentialBackOff.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/ExponentialBackOff.java @@ -70,22 +70,22 @@ import com.google.common.base.Preconditions; */ public class ExponentialBackOff { /** The default initial interval value in milliseconds (0.5 seconds). */ - public static final int DEFAULT_INITIAL_INTERVAL_MILLIS = 500; + private static final int DEFAULT_INITIAL_INTERVAL_MILLIS = 500; /** * The default randomization factor (0.5 which results in a random period ranging between 50% * below and 50% above the retry interval). */ - public static final double DEFAULT_RANDOMIZATION_FACTOR = 0.5; + private static final double DEFAULT_RANDOMIZATION_FACTOR = 0.5; /** The default multiplier value (1.5 which is 50% increase per back off). */ - public static final double DEFAULT_MULTIPLIER = 1.5; + private static final double DEFAULT_MULTIPLIER = 1.5; /** The default maximum back off time in milliseconds (15 minutes). */ - public static final int DEFAULT_MAX_INTERVAL_MILLIS = 30000; + private static final int DEFAULT_MAX_INTERVAL_MILLIS = 30000; /** The default maximum elapsed time in milliseconds (60 minutes). */ - public static final int DEFAULT_MAX_ELAPSED_TIME_MILLIS = 3600000; + private static final int DEFAULT_MAX_ELAPSED_TIME_MILLIS = 3600000; /** The current retry interval in milliseconds. */ private int currentIntervalMillis; @@ -116,7 +116,7 @@ public class ExponentialBackOff { * The system time in nanoseconds. It is calculated when an ExponentialBackOffPolicy instance is * created and is reset when {@link #reset()} is called. */ - long startTimeNanos; + private long startTimeNanos; /** * The maximum elapsed time after instantiating {@link ExponentialBackOff} or calling @@ -139,14 +139,14 @@ public class ExponentialBackOff { *
  • {@code maxElapsedTimeMillis} defaults in {@link #DEFAULT_MAX_ELAPSED_TIME_MILLIS}
  • * */ - public ExponentialBackOff() { + ExponentialBackOff() { this(new Builder()); } /** * @param builder builder */ - protected ExponentialBackOff(Builder builder) { + private ExponentialBackOff(Builder builder) { initialIntervalMillis = builder.initialIntervalMillis; randomizationFactor = builder.randomizationFactor; multiplier = builder.multiplier; @@ -161,7 +161,7 @@ public class ExponentialBackOff { } /** Sets the interval back to the initial retry interval and restarts the timer. */ - public final void reset() { + final void reset() { currentIntervalMillis = initialIntervalMillis; startTimeNanos = nanoTime(); } @@ -178,7 +178,7 @@ public class ExponentialBackOff { * Subclasses may override if a different algorithm is required. *

    */ - public long nextBackOffMillis() { + long nextBackOffMillis() { // Make sure we have not gone over the maximum elapsed time. if (getElapsedTimeMillis() > maxElapsedTimeMillis) { return maxElapsedTimeMillis; @@ -193,7 +193,7 @@ public class ExponentialBackOff { * Returns a random value from the interval [randomizationFactor * currentInterval, * randomizationFactor * currentInterval]. */ - static int getRandomValueFromInterval( + private static int getRandomValueFromInterval( double randomizationFactor, double random, int currentIntervalMillis) { double delta = randomizationFactor * currentIntervalMillis; double minInterval = currentIntervalMillis - delta; @@ -201,60 +201,7 @@ public class ExponentialBackOff { // Get a random value from the range [minInterval, maxInterval]. // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then // we want a 33% chance for selecting either 1, 2 or 3. - int randomValue = (int) (minInterval + (random * (maxInterval - minInterval + 1))); - return randomValue; - } - - /** Returns the initial retry interval in milliseconds. */ - public final int getInitialIntervalMillis() { - return initialIntervalMillis; - } - - /** - * Returns the randomization factor to use for creating a range around the retry interval. - * - *

    - * A randomization factor of 0.5 results in a random period ranging between 50% below and 50% - * above the retry interval. - *

    - */ - public final double getRandomizationFactor() { - return randomizationFactor; - } - - /** - * Returns the current retry interval in milliseconds. - */ - public final int getCurrentIntervalMillis() { - return currentIntervalMillis; - } - - /** - * Returns the value to multiply the current interval with for each retry attempt. - */ - public final double getMultiplier() { - return multiplier; - } - - /** - * Returns the maximum value of the back off period in milliseconds. Once the current interval - * reaches this value it stops increasing. - */ - public final int getMaxIntervalMillis() { - return maxIntervalMillis; - } - - /** - * Returns the maximum elapsed time in milliseconds. - * - *

    - * If the time elapsed since an {@link ExponentialBackOff} instance is created goes past the - * max_elapsed_time then the method {@link #nextBackOffMillis()} starts returning - * this value. The elapsed time can be reset by calling {@link #reset()}. - *

    - */ - public final int getMaxElapsedTimeMillis() { - return maxElapsedTimeMillis; + return (int) (minInterval + (random * (maxInterval - minInterval + 1))); } /** @@ -265,7 +212,7 @@ public class ExponentialBackOff { * The elapsed time is computed using {@link System#nanoTime()}. *

    */ - public final long getElapsedTimeMillis() { + private long getElapsedTimeMillis() { return (nanoTime() - startTimeNanos) / 1000000; } @@ -327,152 +274,12 @@ public class ExponentialBackOff { */ int maxElapsedTimeMillis = DEFAULT_MAX_ELAPSED_TIME_MILLIS; - public Builder() { + Builder() { } /** Builds a new instance of {@link ExponentialBackOff}. */ public ExponentialBackOff build() { return new ExponentialBackOff(this); } - - /** - * Returns the initial retry interval in milliseconds. The default value is - * {@link #DEFAULT_INITIAL_INTERVAL_MILLIS}. - */ - public final int getInitialIntervalMillis() { - return initialIntervalMillis; - } - - /** - * Sets the initial retry interval in milliseconds. The default value is - * {@link #DEFAULT_INITIAL_INTERVAL_MILLIS}. Must be {@code > 0}. - * - *

    - * Overriding is only supported for the purpose of calling the super implementation and changing - * the return type, but nothing else. - *

    - */ - public Builder setInitialIntervalMillis(int initialIntervalMillis) { - this.initialIntervalMillis = initialIntervalMillis; - return this; - } - - /** - * Returns the randomization factor to use for creating a range around the retry interval. The - * default value is {@link #DEFAULT_RANDOMIZATION_FACTOR}. - * - *

    - * A randomization factor of 0.5 results in a random period ranging between 50% below and 50% - * above the retry interval. - *

    - * - *

    - * Overriding is only supported for the purpose of calling the super implementation and changing - * the return type, but nothing else. - *

    - */ - public final double getRandomizationFactor() { - return randomizationFactor; - } - - /** - * Sets the randomization factor to use for creating a range around the retry interval. The - * default value is {@link #DEFAULT_RANDOMIZATION_FACTOR}. Must fall in the range - * {@code 0 <= randomizationFactor < 1}. - * - *

    - * A randomization factor of 0.5 results in a random period ranging between 50% below and 50% - * above the retry interval. - *

    - * - *

    - * Overriding is only supported for the purpose of calling the super implementation and changing - * the return type, but nothing else. - *

    - */ - public Builder setRandomizationFactor(double randomizationFactor) { - this.randomizationFactor = randomizationFactor; - return this; - } - - /** - * Returns the value to multiply the current interval with for each retry attempt. The default - * value is {@link #DEFAULT_MULTIPLIER}. - */ - public final double getMultiplier() { - return multiplier; - } - - /** - * Sets the value to multiply the current interval with for each retry attempt. The default - * value is {@link #DEFAULT_MULTIPLIER}. Must be {@code >= 1}. - * - *

    - * Overriding is only supported for the purpose of calling the super implementation and changing - * the return type, but nothing else. - *

    - */ - public Builder setMultiplier(double multiplier) { - this.multiplier = multiplier; - return this; - } - - /** - * Returns the maximum value of the back off period in milliseconds. Once the current interval - * reaches this value it stops increasing. The default value is - * {@link #DEFAULT_MAX_INTERVAL_MILLIS}. Must be {@code >= initialInterval}. - */ - public final int getMaxIntervalMillis() { - return maxIntervalMillis; - } - - /** - * Sets the maximum value of the back off period in milliseconds. Once the current interval - * reaches this value it stops increasing. The default value is - * {@link #DEFAULT_MAX_INTERVAL_MILLIS}. - * - *

    - * Overriding is only supported for the purpose of calling the super implementation and changing - * the return type, but nothing else. - *

    - */ - public Builder setMaxIntervalMillis(int maxIntervalMillis) { - this.maxIntervalMillis = maxIntervalMillis; - return this; - } - - /** - * Returns the maximum elapsed time in milliseconds. The default value is - * {@link #DEFAULT_MAX_ELAPSED_TIME_MILLIS}. - * - *

    - * If the time elapsed since an {@link ExponentialBackOff} instance is created goes past the - * max_elapsed_time then the method {@link #nextBackOffMillis()} starts returning - * this value. The elapsed time can be reset by calling {@link #reset()}. - *

    - */ - public final int getMaxElapsedTimeMillis() { - return maxElapsedTimeMillis; - } - - /** - * Sets the maximum elapsed time in milliseconds. The default value is - * {@link #DEFAULT_MAX_ELAPSED_TIME_MILLIS}. Must be {@code > 0}. - * - *

    - * If the time elapsed since an {@link ExponentialBackOff} instance is created goes past the - * max_elapsed_time then the method {@link #nextBackOffMillis()} starts returning - * this value. The elapsed time can be reset by calling {@link #reset()}. - *

    - * - *

    - * Overriding is only supported for the purpose of calling the super implementation and changing - * the return type, but nothing else. - *

    - */ - public Builder setMaxElapsedTimeMillis(int maxElapsedTimeMillis) { - this.maxElapsedTimeMillis = maxElapsedTimeMillis; - return this; - } } } \ No newline at end of file diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/RestApi.java b/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/RestApi.java index baf3a046..35fbc7f1 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/RestApi.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/RestApi.java @@ -1,12 +1,11 @@ package org.telegram.telegrambots.updatesreceivers; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.telegram.telegrambots.meta.api.methods.BotApiMethod; import org.telegram.telegrambots.meta.api.objects.Update; import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException; import org.telegram.telegrambots.meta.generics.WebhookBot; -import org.telegram.telegrambots.meta.logging.BotLogger; - -import java.util.concurrent.ConcurrentHashMap; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -16,15 +15,16 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.util.concurrent.ConcurrentHashMap; /** * @author Ruben Bermudez * @version 1.0 - * @brief Rest api to for webhook callback function - * @date 20 of June of 2015 + * Rest api to for webhook callback function */ @Path("callback") public class RestApi { + private static final Logger log = LogManager.getLogger(RestApi.class); private final ConcurrentHashMap callbacks = new ConcurrentHashMap<>(); @@ -50,7 +50,7 @@ public class RestApi { } return Response.ok(response).build(); } catch (TelegramApiValidationException e) { - BotLogger.severe("RESTAPI", e); + log.error(e.getLocalizedMessage(), e); return Response.serverError().build(); } }