[AB-57] [AB-61] reworked commands and services to work with completable futures and moved the database operations to the very last operation so we have transaction safety in more areas

added some cache annotations to the default repository functions
reworked how the undo cations are processed within commands, they are executed in a post command listener when the state is error
added a counter id to generate ids to be unique within servers, changed a few tables to be unique within a server
added future utils class for wrapping a list of futures into one
moved abstracto tables to separate schema in the installer
refactored experience gain to work with more futures and delayed database access
This commit is contained in:
Sheldan
2020-09-20 11:11:20 +02:00
parent 552ecc26b8
commit 76adda90a3
220 changed files with 2691 additions and 1498 deletions

View File

@@ -23,8 +23,8 @@ public class CommandDisabledCondition implements CommandCondition {
ACommand acommand = commandManagementService.findCommandByName(command.getConfiguration().getName());
Boolean booleanResult = channelGroupCommandService.isCommandEnabled(acommand, context.getUserInitiatedContext().getChannel());
if(!booleanResult) {
throw new CommandDisabledException();
return ConditionResult.builder().result(true).exception(new CommandDisabledException()).build();
}
return ConditionResult.builder().result(booleanResult).reason("Command is disabled.").build();
return ConditionResult.builder().result(true).reason("Command is disabled.").build();
}
}

View File

@@ -45,6 +45,7 @@ public class CommandDisallowedCondition implements CommandCondition {
}
}
List<Role> allowedRoles = roleService.getRolesFromGuild(commandForServer.getAllowedRoles());
throw new InsufficientPermissionException(allowedRoles);
InsufficientPermissionException exception = new InsufficientPermissionException(allowedRoles);
return ConditionResult.builder().result(false).exception(exception).build();
}
}

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.core.command.condition;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@@ -10,4 +11,6 @@ import lombok.Setter;
public class ConditionResult {
private boolean result;
private String reason;
private Object additionalInfo;
private AbstractoRunTimeException exception;
}

View File

@@ -26,13 +26,13 @@ public class FeatureEnabledCondition implements CommandCondition {
public ConditionResult shouldExecute(CommandContext context, Command command) {
FeatureEnum feature = command.getFeature();
boolean featureFlagValue = true;
String reason = "";
if(feature != null) {
featureFlagValue = featureFlagService.getFeatureFlagValue(feature, context.getGuild().getIdLong());
if(!featureFlagValue) {
throw new FeatureDisabledException(featureConfigService.getFeatureDisplayForFeature(feature));
FeatureDisabledException exception = new FeatureDisabledException(featureConfigService.getFeatureDisplayForFeature(command.getFeature()));
return ConditionResult.builder().result(false).exception(exception).build();
}
}
return ConditionResult.builder().reason(reason).result(featureFlagValue).build();
return ConditionResult.builder().result(true).build();
}
}

View File

@@ -41,7 +41,8 @@ public class ImmuneUserCondition implements CommandCondition {
Member member = any.get();
for (ARole role : commandForServer.getImmuneRoles()) {
if (roleService.memberHasRole(member, role)) {
throw new ImmuneUserException(roleService.getRoleFromGuild(role));
ImmuneUserException exception = new ImmuneUserException(roleService.getRoleFromGuild(role));
return ConditionResult.builder().result(false).exception(exception).build();
}
}
}

View File

@@ -13,6 +13,11 @@ public class AbstractoTemplatedException extends AbstractoRunTimeException imple
this.templateKey = templateKey;
}
public AbstractoTemplatedException(String message, String templateKey, Throwable cause) {
super(message, cause);
this.templateKey = templateKey;
}
@Override
public String getTemplateName() {
return templateKey;

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.core.command.execution;
import dev.sheldan.abstracto.core.command.config.Parameters;
import dev.sheldan.abstracto.core.models.UndoActionInstance;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import lombok.Builder;
import lombok.Getter;
@@ -11,6 +12,8 @@ import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import java.util.List;
@Builder
@Getter
@Setter
@@ -22,4 +25,5 @@ public class CommandContext {
private UserInitiatedServerContext userInitiatedContext;
private Parameters parameters;
private JDA jda;
private List<UndoActionInstance> undoActions;
}

View File

@@ -1,6 +1,7 @@
package dev.sheldan.abstracto.core.command.execution;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.models.context.SlimUserInitiatedServerContext;
import dev.sheldan.abstracto.core.models.context.UserInitiatedServerContext;
import lombok.extern.slf4j.Slf4j;
@@ -34,4 +35,21 @@ public class ContextConverter {
}
throw new AbstractoRunTimeException("Failed to create model from context");
}
public static <T extends SlimUserInitiatedServerContext> SlimUserInitiatedServerContext slimFromCommandContext(CommandContext commandContext, Class<T> clazz) {
Method m = null;
try {
m = clazz.getMethod("builder");
SlimUserInitiatedServerContext.SlimUserInitiatedServerContextBuilder<?, ?> builder = (SlimUserInitiatedServerContext.SlimUserInitiatedServerContextBuilder) m.invoke(null, null);
return builder
.member(commandContext.getAuthor())
.guild(commandContext.getGuild())
.message(commandContext.getMessage())
.channel(commandContext.getChannel())
.build();
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log.error("Failed to execute builder method", e);
}
throw new AbstractoRunTimeException("Failed to create model from context");
}
}

View File

@@ -22,6 +22,6 @@ public class InteractiveUtils {
public void sendTimeoutMessage(Long serverId, Long channelId) {
String s = templateService.renderSimpleTemplate("setup_configuration_timeout");
Optional<TextChannel> channelOptional = channelService.getTextChannelInGuild(serverId, channelId);
channelOptional.ifPresent(channel -> channelService.sendTextToChannelNoFuture(s, channel));
channelOptional.ifPresent(channel -> channelService.sendTextToChannelNotAsync(s, channel));
}
}

View File

@@ -0,0 +1,28 @@
package dev.sheldan.abstracto.core.models;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;
@Embeddable
public class CounterId implements Serializable {
@Column(name = "server_id")
private Long serverId;
@Column(name = "counter_key")
private String counterKey;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CounterId counterId = (CounterId) o;
return Objects.equals(serverId, counterId.serverId) &&
Objects.equals(counterKey, counterId.counterKey);
}
@Override
public int hashCode() {
return Objects.hash(serverId, counterKey);
}
}

View File

@@ -0,0 +1,57 @@
package dev.sheldan.abstracto.core.models;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;
@Embeddable
@Getter
@Setter
public class ServerSpecificId implements Serializable {
@Column(name = "server_id")
private Long serverId;
@Column(name = "id")
private Long id;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerSpecificId that = (ServerSpecificId) o;
return Objects.equals(serverId, that.serverId) &&
Objects.equals(id, that.id);
}
public ServerSpecificId() {
}
public ServerSpecificId(Long serverId, Long id) {
this.serverId = serverId;
this.id = id;
}
@Override
public int hashCode() {
return Objects.hash(serverId, id);
}
public Long getServerId() {
return serverId;
}
public void setServerId(Long serverId) {
this.serverId = serverId;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.abstracto.core.models.context;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Guild;
@Getter
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@Setter
public class SlimServerContext {
private Guild guild;
}

View File

@@ -0,0 +1,19 @@
package dev.sheldan.abstracto.core.models.context;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
@Getter
@NoArgsConstructor
@Setter
@SuperBuilder
public class SlimUserInitiatedServerContext extends SlimServerContext {
private MessageChannel channel;
private Member member;
private Message message;
}

View File

@@ -11,7 +11,8 @@ import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
@Getter @NoArgsConstructor
@Getter
@NoArgsConstructor
@Setter
@SuperBuilder
public class UserInitiatedServerContext extends ServerContext {

View File

@@ -0,0 +1,30 @@
package dev.sheldan.abstracto.core.models.database;
import dev.sheldan.abstracto.core.models.CounterId;
import lombok.*;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
@Entity
@Table(name = "counter")
@Getter
@Builder
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Counter {
@EmbeddedId
private CounterId counterId;
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@MapsId("serverId")
@JoinColumn(name = "serverReference", referencedColumnName = "id", nullable = false)
private AServer server;
@Column(name = "counter")
private Long counter;
}

View File

@@ -13,8 +13,8 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public interface ChannelService {
void sendTextToAChannelNoFuture(String text, AChannel channel);
void sendTextToChannelNoFuture(String text, MessageChannel channel);
void sendTextToAChannelNotAsync(String text, AChannel channel);
void sendTextToChannelNotAsync(String text, MessageChannel channel);
CompletableFuture<Message> sendTextToAChannel(String text, AChannel channel);
CompletableFuture<Message> sendMessageToAChannel(Message message, AChannel channel);
CompletableFuture<Message> sendMessageToChannel(Message message, MessageChannel channel);
@@ -36,6 +36,7 @@ public interface ChannelService {
CompletableFuture<Void> deleteTextChannel(AChannel channel);
CompletableFuture<Void> deleteTextChannel(Long serverId, Long channelId);
List<CompletableFuture<Message>> sendEmbedTemplateInChannel(String templateKey, Object model, MessageChannel channel);
CompletableFuture<Message> sendTextTemplateInChannel(String templateKey, Object model, MessageChannel channel);
CompletableFuture<TextChannel> createTextChannel(String name, AServer server, Long categoryId);
Optional<TextChannel> getChannelFromAChannel(AChannel channel);

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.database.AServer;
public interface CounterService {
Long getNextCounterValue(AServer server, String key);
}

View File

@@ -4,10 +4,7 @@ import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AEmote;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.templating.model.MessageToSend;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -15,7 +12,9 @@ import java.util.concurrent.CompletableFuture;
public interface MessageService {
void addReactionToMessage(String emoteKey, Long serverId, Message message);
CompletableFuture<Void> addReactionToMessageWithFuture(String emoteKey, Long serverId, Message message);
CompletableFuture<Void> addReactionToMessageWithFuture(String emoteKey, Guild guild, Message message);
CompletableFuture<Void> addReactionToMessageWithFuture(AEmote emote, Long serverId, Message message);
CompletableFuture<Void> addReactionToMessageWithFuture(AEmote emote, Guild guild, Message message);
CompletableFuture<Void> addReactionToMessageWithFuture(Long emoteId, Long serverId, Message message);
CompletableFuture<Void> removeReactionFromMessageWithFuture(AEmote emote, Long serverId, Message message);
CompletableFuture<Void> clearReactionFromMessageWithFuture(AEmote emote, Message message);
@@ -36,6 +35,9 @@ public interface MessageService {
CompletableFuture<Long> createStatusMessageId(MessageToSend messageToSend, MessageChannel channel);
void updateStatusMessage(AChannel channel, Long messageId, MessageToSend messageToSend);
void updateStatusMessage(MessageChannel channel, Long messageId, MessageToSend messageToSend);
void sendMessageToUser(AUserInAServer userInAServer, String text, MessageChannel feedbackChannel);
void sendMessageToUser(User user, String text, MessageChannel feedbackChannel);
CompletableFuture<Message> sendMessageToUser(AUserInAServer userInAServer, String text);
CompletableFuture<Message> sendTemplateToUser(User user, String template, Object model);
CompletableFuture<Void> sendEmbedToUser(User user, String template, Object model);
CompletableFuture<Message> sendEmbedToUserWithMessage(User user, String template, Object model);
CompletableFuture<Message> sendMessageToUser(User user, String text);
}

View File

@@ -3,7 +3,9 @@ package dev.sheldan.abstracto.core.service;
import dev.sheldan.abstracto.core.models.UndoActionInstance;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface UndoActionService {
void performActions(List<UndoActionInstance> actionsToPerform);
CompletableFuture<Void> performActionsFuture(List<UndoActionInstance> actionsToPerform);
}

View File

@@ -12,7 +12,8 @@ public interface UserInServerManagementService {
AUserInAServer loadUser(Long serverId, Long userId);
AUserInAServer loadUser(AServer server, AUser user);
AUserInAServer loadUser(Member member);
Optional<AUserInAServer> loadUser(Long userInServerId);
Optional<AUserInAServer> loadUserConditional(Long userInServerId);
AUserInAServer loadUser(Long userInServerId);
AUserInAServer createUserInServer(Member member);
AUserInAServer createUserInServer(Long guildId, Long userId);
List<AUserInAServer> getUserInAllServers(Long userId);

View File

@@ -1,16 +1,34 @@
package dev.sheldan.abstracto.core.utils;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Getter
@Setter
@Builder
@Slf4j
public class CompletableFutureList<T> {
private CompletableFuture<Void> mainFuture;
private List<CompletableFuture<T>> futures;
public CompletableFutureList(List<CompletableFuture<T>> futures) {
this.mainFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
this.futures = futures;
}
public List<T> getObjects() {
List<T> result = new ArrayList<>();
futures.forEach(future -> {
if(!future.isCompletedExceptionally()) {
result.add(future.join());
} else {
log.warn("Future completed with exception {}.", future.join());
}
});
return result;
}
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.core.utils;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@NoArgsConstructor
public class FutureUtils {
public static <T> CompletableFuture<Void> toSingleFutureGeneric(List<CompletableFuture<T>> futures) {
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
}