[AB-78] adding new concept of feature modes, with splitting it up to new commands and default mode concept

refactoring command received handler to only load the entities in the same thread as the actual executed commands, so that user initiated context contains valid references from the same thread
updating documentation
fixing issue when the result of role calculation result in no experience role id
This commit is contained in:
Sheldan
2020-10-15 00:42:42 +02:00
parent bc9afc9bfc
commit 0aa7d3f036
134 changed files with 1934 additions and 473 deletions

View File

@@ -29,7 +29,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@@ -82,13 +81,44 @@ public class CommandReceivedHandler extends ListenerAdapter {
@Override
@Transactional
public void onMessageReceived(@Nonnull MessageReceivedEvent event) {
public void onMessageReceived(MessageReceivedEvent event) {
if(!event.isFromGuild()) {
return;
}
if(!commandManager.isCommand(event.getMessage())) {
return;
}
final Command foundCommand;
try {
String contentStripped = event.getMessage().getContentRaw();
List<String> parameters = Arrays.asList(contentStripped.split(" "));
UnParsedCommandParameter unParsedParameter = new UnParsedCommandParameter(contentStripped);
String commandName = commandManager.getCommandName(parameters.get(0), event.getGuild().getIdLong());
foundCommand = commandManager.findCommandByParameters(commandName, unParsedParameter);
tryToExecuteFoundCommand(event, foundCommand, unParsedParameter);
} catch (Exception e) {
reportException(event, null, e, "Exception when executing command.");
}
}
private void tryToExecuteFoundCommand(MessageReceivedEvent event, Command foundCommand, UnParsedCommandParameter unParsedParameter) {
CompletableFuture<Parameters> parsingFuture = getParsedParameters(unParsedParameter, foundCommand, event.getMessage());
parsingFuture.thenAccept(parsedParameters ->
self.executeCommand(event, foundCommand, parsedParameters)
).exceptionally(throwable -> {
reportException(event, foundCommand, throwable, "Exception when executing command.");
return null;
});
parsingFuture.exceptionally(throwable -> {
self.reportException(event, foundCommand, throwable, "Exception when parsing command.");
return null;
});
}
@Transactional
public void executeCommand(MessageReceivedEvent event, Command foundCommand, Parameters parsedParameters) {
UserInitiatedServerContext userInitiatedContext = buildTemplateParameter(event);
CommandContext.CommandContextBuilder commandContextBuilder = CommandContext.builder()
.author(event.getMember())
@@ -98,73 +128,58 @@ public class CommandReceivedHandler extends ListenerAdapter {
.message(event.getMessage())
.jda(event.getJDA())
.userInitiatedContext(userInitiatedContext);
final Command foundCommand;
try {
String contentStripped = event.getMessage().getContentRaw();
List<String> parameters = Arrays.asList(contentStripped.split(" "));
UnParsedCommandParameter unParsedParameter = new UnParsedCommandParameter(contentStripped);
String commandName = commandManager.getCommandName(parameters.get(0), event.getGuild().getIdLong());
foundCommand = commandManager.findCommandByParameters(commandName, unParsedParameter);
tryToExecuteFoundCommand(event, commandContextBuilder, foundCommand, unParsedParameter);
validateCommandParameters(parsedParameters, foundCommand);
CommandContext commandContext = commandContextBuilder.parameters(parsedParameters).build();
ConditionResult conditionResult = commandService.isCommandExecutable(foundCommand, commandContext);
CommandResult commandResult = null;
if(conditionResult.isResult()) {
if(foundCommand.getConfiguration().isAsync()) {
log.info("Executing async command {} for server {} in channel {} based on message {} by user {}.",
foundCommand.getConfiguration().getName(), commandContext.getGuild().getId(), commandContext.getChannel().getId(), commandContext.getMessage().getId(), commandContext.getAuthor().getId());
} catch (Exception e) {
reportException(commandContextBuilder, null, e, "Exception when executing command.");
foundCommand.executeAsync(commandContext).thenAccept(result ->
executePostCommandListener(foundCommand, commandContext, result)
).exceptionally(throwable -> {
log.error("Asynchronous command {} failed.", foundCommand.getConfiguration().getName(), throwable);
UserInitiatedServerContext rebuildUserContext = buildTemplateParameter(event);
CommandContext rebuildContext = CommandContext.builder()
.author(event.getMember())
.guild(event.getGuild())
.channel(event.getTextChannel())
.message(event.getMessage())
.jda(event.getJDA())
.undoActions(commandContext.getUndoActions()) // TODO really do this? it would need to guarantee that its available and usable
.userInitiatedContext(rebuildUserContext)
.parameters(parsedParameters).build();
CommandResult failedResult = CommandResult.fromError(throwable.getMessage(), throwable);
self.executePostCommandListener(foundCommand, rebuildContext, failedResult);
return null;
});
} else {
commandResult = self.executeCommand(foundCommand, commandContext);
}
} else {
// TODO can it be done nicer?
if(conditionResult.getException() != null) {
throw conditionResult.getException();
}
}
if(commandResult != null) {
self.executePostCommandListener(foundCommand, commandContext, commandResult);
}
}
private void tryToExecuteFoundCommand(@Nonnull MessageReceivedEvent event, CommandContext.CommandContextBuilder commandContextBuilder, Command foundCommand, UnParsedCommandParameter unParsedParameter) {
CompletableFuture<Parameters> parsingFuture = getParsedParameters(unParsedParameter, foundCommand, event.getMessage());
parsingFuture.thenAccept(parsedParameters -> {
validateCommandParameters(parsedParameters, foundCommand);
CommandContext commandContext = commandContextBuilder.parameters(parsedParameters).build();
ConditionResult conditionResult = commandService.isCommandExecutable(foundCommand, commandContext);
CommandResult commandResult = null;
if(conditionResult.isResult()) {
if(foundCommand.getConfiguration().isAsync()) {
log.info("Executing async command {} for server {} in channel {} based on message {} by user {}.",
foundCommand.getConfiguration().getName(), commandContext.getGuild().getId(), commandContext.getChannel().getId(), commandContext.getMessage().getId(), commandContext.getAuthor().getId());
foundCommand.executeAsync(commandContext).thenAccept(result ->
executePostCommandListener(foundCommand, commandContext, result)
).exceptionally(throwable -> {
log.error("Asynchronous command {} failed.", foundCommand.getConfiguration().getName(), throwable);
UserInitiatedServerContext rebuildUserContext = buildTemplateParameter(event);
CommandContext rebuildContext = CommandContext.builder()
.author(event.getMember())
.guild(event.getGuild())
.channel(event.getTextChannel())
.message(event.getMessage())
.jda(event.getJDA())
.undoActions(commandContext.getUndoActions()) // TODO really do this? it would need to guarantee that its available and usable
.userInitiatedContext(rebuildUserContext)
.parameters(parsedParameters).build();
CommandResult failedResult = CommandResult.fromError(throwable.getMessage(), throwable);
self.executePostCommandListener(foundCommand, rebuildContext, failedResult);
return null;
});
} else {
commandResult = self.executeCommand(foundCommand, commandContext);
}
} else {
// TODO can it be done nicer?
if(conditionResult.getException() != null) {
throw conditionResult.getException();
}
}
if(commandResult != null) {
self.executePostCommandListener(foundCommand, commandContext, commandResult);
}
}).exceptionally(throwable -> {
reportException(commandContextBuilder, foundCommand, throwable, "Exception when executing command.");
return null;
});
parsingFuture.exceptionally(throwable -> {
reportException(commandContextBuilder, foundCommand, throwable, "Exception when parsing command.");
return null;
});
}
private void reportException(CommandContext.CommandContextBuilder commandContextBuilder, Command foundCommand, Throwable throwable, String s) {
@Transactional
public void reportException(MessageReceivedEvent event, Command foundCommand, Throwable throwable, String s) {
UserInitiatedServerContext userInitiatedContext = buildTemplateParameter(event);
CommandContext.CommandContextBuilder commandContextBuilder = CommandContext.builder()
.author(event.getMember())
.guild(event.getGuild())
.undoActions(new ArrayList<>())
.channel(event.getTextChannel())
.message(event.getMessage())
.jda(event.getJDA())
.userInitiatedContext(userInitiatedContext);
log.error(s, throwable);
CommandResult commandResult = CommandResult.fromError(throwable.getMessage(), throwable);
CommandContext commandContext = commandContextBuilder.build();

View File

@@ -0,0 +1,74 @@
package dev.sheldan.abstracto.core.commands.config.features;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.management.FeatureModeManagementService;
import dev.sheldan.abstracto.templating.service.TemplateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class DisableMode extends AbstractConditionableCommand {
@Autowired
private FeatureManagementService featureManagementService;
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private FeatureModeManagementService featureModeManagementService;
@Autowired
private TemplateService templateService;
@Override
public CommandResult execute(CommandContext commandContext) {
checkParameters(commandContext);
String featureName = (String) commandContext.getParameters().getParameters().get(0);
String modeName = (String) commandContext.getParameters().getParameters().get(1);
FeatureEnum featureEnum = featureConfigService.getFeatureEnum(featureName);
FeatureMode featureMode = featureModeService.getFeatureModeForKey(modeName);
featureModeService.disableFeatureModeForFeature(featureEnum, commandContext.getUserInitiatedContext().getServer(), featureMode);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter featureName = Parameter.builder().name("feature").type(String.class).templated(true).build();
Parameter mode = Parameter.builder().name("mode").type(String.class).templated(true).build();
List<Parameter> parameters = Arrays.asList(featureName, mode);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("disableMode")
.module(ConfigModuleInterface.CONFIG)
.parameters(parameters)
.templated(true)
.supportsEmbedException(true)
.help(helpInfo)
.causesReaction(true)
.build();
}
@Override
public FeatureEnum getFeature() {
return CoreFeatures.CORE_FEATURE;
}
}

View File

@@ -10,9 +10,10 @@ import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService;
import dev.sheldan.abstracto.core.service.management.FeatureModeManagementService;
import dev.sheldan.abstracto.templating.service.TemplateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -21,7 +22,7 @@ import java.util.Arrays;
import java.util.List;
@Component
public class SetMode extends AbstractConditionableCommand {
public class EnableMode extends AbstractConditionableCommand {
@Autowired
private FeatureManagementService featureManagementService;
@@ -33,31 +34,34 @@ public class SetMode extends AbstractConditionableCommand {
private FeatureModeService featureModeService;
@Autowired
private FeatureFlagManagementService featureFlagManagementService;
private FeatureModeManagementService featureModeManagementService;
@Autowired
private TemplateService templateService;
@Override
public CommandResult execute(CommandContext commandContext) {
checkParameters(commandContext);
String featureName = (String) commandContext.getParameters().getParameters().get(0);
String modeName = (String) commandContext.getParameters().getParameters().get(1);
featureModeService.setModeForFeatureTo(featureName, commandContext.getUserInitiatedContext().getServer(), modeName);
FeatureEnum featureEnum = featureConfigService.getFeatureEnum(featureName);
FeatureMode featureMode = featureModeService.getFeatureModeForKey(modeName);
featureModeService.enableFeatureModeForFeature(featureEnum, commandContext.getUserInitiatedContext().getServer(), featureMode);
return CommandResult.fromSuccess();
}
@Override
public CommandConfiguration getConfiguration() {
Parameter featureName = Parameter.builder().name("feature").type(String.class).templated(true).build();
Parameter newMode = Parameter.builder().name("newMode").type(String.class).templated(true).build();
List<Parameter> parameters = Arrays.asList(featureName, newMode);
HelpInfo helpInfo = HelpInfo.builder().templated(true).hasExample(true).build();
Parameter mode = Parameter.builder().name("mode").type(String.class).templated(true).build();
List<Parameter> parameters = Arrays.asList(featureName, mode);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("setMode")
.name("enableMode")
.module(ConfigModuleInterface.CONFIG)
.parameters(parameters)
.templated(true)
.supportsEmbedException(true)
.help(helpInfo)
.causesReaction(true)
.build();

View File

@@ -0,0 +1,81 @@
package dev.sheldan.abstracto.core.commands.config.features;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.config.features.CoreFeatures;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleInterface;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.template.commands.FeatureModeDisplay;
import dev.sheldan.abstracto.core.models.template.commands.FeatureModesModel;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class FeatureModes extends AbstractConditionableCommand {
public static final String FEATURE_MODES_RESPONSE_TEMPLATE_KEY = "feature_modes_response";
@Autowired
private FeatureConfigService featureConfigService;
@Autowired
private FeatureManagementService featureManagementService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private ChannelService channelService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
checkParameters(commandContext);
List<FeatureModeDisplay> featureModes;
if(commandContext.getParameters().getParameters().isEmpty()) {
featureModes = featureModeService.getEffectiveFeatureModes(commandContext.getUserInitiatedContext().getServer());
} else {
String featureName = (String) commandContext.getParameters().getParameters().get(0);
FeatureEnum featureEnum = featureConfigService.getFeatureEnum(featureName);
AFeature feature = featureManagementService.getFeature(featureEnum.getKey());
featureModes = featureModeService.getEffectiveFeatureModes(commandContext.getUserInitiatedContext().getServer(), feature);
}
FeatureModesModel model = FeatureModesModel.builder().featureModes(featureModes).build();
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInChannel(FEATURE_MODES_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(aVoid -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter featureName = Parameter.builder().name("feature").type(String.class).optional(true).templated(true).build();
List<Parameter> parameters = Arrays.asList(featureName);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("featureModes")
.module(ConfigModuleInterface.CONFIG)
.parameters(parameters)
.templated(true)
.supportsEmbedException(true)
.help(helpInfo)
.async(true)
.causesReaction(true)
.build();
}
@Override
public FeatureEnum getFeature() {
return CoreFeatures.CORE_FEATURE;
}
}

View File

@@ -1,18 +1,13 @@
package dev.sheldan.abstracto.core.listener;
import dev.sheldan.abstracto.core.command.service.management.FeatureManagementService;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.management.DefaultFeatureFlagManagementService;
import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService;
import dev.sheldan.abstracto.core.service.management.FeatureModeManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
@@ -21,43 +16,19 @@ public class FeatureFlagListener implements ServerConfigListener {
@Autowired
private FeatureFlagManagementService service;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private FeatureModeManagementService featureModeManagementService;
@Autowired
private FeatureManagementService featureManagementService;
@Autowired
private Environment environment;
@Autowired
private FeatureFlagManagementService featureFlagManagementService;
@Autowired
private DefaultFeatureFlagManagementService defaultFeatureFlagManagementService;
@Override
public void updateServerConfig(AServer server) {
log.info("Setting up feature flags if necessary.");
List<String> defaultFeatureKeys = defaultFeatureFlagManagementService.getDefaultFeatureKeys();
defaultFeatureFlagManagementService.getAllDefaultFeatureFlags().forEach(featureFlagKey -> {
String featureKey = featureFlagKey.getFeature().getKey();
AFeature feature = featureManagementService.getFeature(featureKey);
if(defaultFeatureKeys.contains(featureKey)) {
if(service.getFeatureFlag(feature, server.getId()) == null) {
log.info("Creating feature flag {} for server {}.", feature.getKey(), server.getId());
service.createFeatureFlag(feature, server.getId(), featureFlagKey.isEnabled());
} else {
log.trace("Feature flag {} for server {} already exists.", feature.getKey(), server.getId());
}
if(featureFlagKey.getMode() != null && !featureModeManagementService.featureModeSet(feature, server)) {
featureModeService.createMode(feature, server, featureFlagKey.getMode());
}
defaultFeatureFlagManagementService.getAllDefaultFeatureFlags().forEach(defaultFeatureFlag -> {
AFeature feature = defaultFeatureFlag.getFeature();
if(!service.featureFlagExists(feature, server)) {
log.info("Creating feature flag {} for server {}.", feature.getKey(), server.getId());
service.createFeatureFlag(feature, server.getId(), defaultFeatureFlag.isEnabled());
} else {
log.warn("Feature {} was found as interface, but not in the properties configuration. It will not be setup.", featureKey);
log.trace("Feature flag {} for server {} already exists.", feature.getKey(), server.getId());
}
});
}

View File

@@ -0,0 +1,20 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.DefaultFeatureMode;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
import java.util.Optional;
@Repository
public interface DefaultFeatureModeRepository extends JpaRepository<DefaultFeatureMode, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<DefaultFeatureMode> findByFeature(AFeature feature);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
Optional<DefaultFeatureMode> findByFeatureAndMode(AFeature feature, String mode);
}

View File

@@ -14,6 +14,9 @@ public interface FeatureFlagRepository extends JpaRepository<AFeatureFlag, Long>
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
AFeatureFlag findByServerAndFeature(AServer server, AFeature key);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsByServerAndFeature(AServer server, AFeature key);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<AFeatureFlag> findAllByServer(AServer server);
}

View File

@@ -1,7 +1,6 @@
package dev.sheldan.abstracto.core.repository;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.database.AFeatureMode;
import dev.sheldan.abstracto.core.models.database.AServer;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -9,12 +8,19 @@ import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.stereotype.Repository;
import javax.persistence.QueryHint;
import java.util.List;
import java.util.Optional;
@Repository
public interface FeatureModeRepository extends JpaRepository<AFeatureMode, Long> {
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
AFeatureMode findByFeatureFlag(AFeatureFlag featureFlag);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
boolean existsByFeatureFlag_ServerAndFeatureFlag_Feature(AServer server, AFeature feature);
Optional<AFeatureMode> findByServerAndFeatureFlag_FeatureAndFeatureMode_Mode(AServer server, AFeature feature, String mode);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<AFeatureMode> findByServer(AServer server);
@QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
List<AFeatureMode> findByServerAndFeatureFlag_Feature(AServer server, AFeature feature);
}

View File

@@ -8,6 +8,7 @@ import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.FeatureModeNotFoundException;
import dev.sheldan.abstracto.core.exception.FeatureNotFoundException;
import dev.sheldan.abstracto.core.models.FeatureValidationResult;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -131,6 +132,11 @@ public class FeatureConfigServiceBean implements FeatureConfigService {
throw new FeatureModeNotFoundException(key, getFeatureModesFromFeatureAsString(featureConfig.getFeature().getKey()));
}
@Override
public FeatureConfig getFeatureConfigForFeature(AFeature feature) {
return getFeatureDisplayForFeature(feature.getKey());
}
@Override
public boolean isModeValid(String featureName, String modeName) {
return availableFeatures

View File

@@ -4,15 +4,22 @@ import dev.sheldan.abstracto.core.command.service.management.FeatureManagementSe
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureEnum;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.database.AFeatureMode;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.exception.FeatureModeNotFoundException;
import dev.sheldan.abstracto.core.models.database.*;
import dev.sheldan.abstracto.core.models.template.commands.FeatureModeDisplay;
import dev.sheldan.abstracto.core.service.management.DefaultFeatureModeManagement;
import dev.sheldan.abstracto.core.service.management.FeatureFlagManagementService;
import dev.sheldan.abstracto.core.service.management.FeatureModeManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@Component
public class FeatureModeServiceBean implements FeatureModeService {
@@ -28,105 +35,127 @@ public class FeatureModeServiceBean implements FeatureModeService {
@Autowired
private FeatureModeManagementService featureModeManagementService;
@Override
public AFeatureMode setModeForFeatureTo(String key, AServer server, String newMode) {
FeatureEnum featureEnum = featureConfigService.getFeatureEnum(key);
return setModeForFeatureTo(featureEnum, server, newMode);
}
@Autowired
private DefaultFeatureModeManagement defaultFeatureModeManagement;
@Override
public AFeatureMode setModeForFeatureTo(AFeatureFlag flag, String newMode) {
FeatureConfig featureConfig = featureConfigService.getFeatureDisplayForFeature(flag.getFeature().getKey());
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(featureConfig, newMode);
return setModeForFeatureTo(flag, featureMode);
}
@Override
public AFeatureMode setModeForFeatureTo(FeatureEnum featureEnum, AServer server, String newMode) {
FeatureConfig featureConfig = featureConfigService.getFeatureDisplayForFeature(featureEnum);
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(featureConfig, newMode);
return setModeForFeatureTo(featureEnum, server, featureMode);
}
@Override
public AFeatureMode setModeForFeatureTo(AFeature feature, AServer server, String newMode) {
FeatureConfig featureConfig = featureConfigService.getFeatureDisplayForFeature(feature.getKey());
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(featureConfig, newMode);
return setModeForFeatureTo(feature, server, featureMode);
}
@Override
public AFeatureMode setModeForFeatureTo(FeatureEnum featureEnum, AServer server, FeatureMode mode) {
public void enableFeatureModeForFeature(FeatureEnum featureEnum, AServer server, FeatureMode mode) {
AFeature feature = featureManagementService.getFeature(featureEnum.getKey());
return setModeForFeatureTo(feature, server, mode);
Optional<AFeatureMode> existing = featureModeManagementService.getFeatureMode(feature, server, mode);
if(existing.isPresent()) {
existing.get().setEnabled(true);
} else {
AFeatureFlag featureFlag = featureFlagManagementService.getFeatureFlag(feature, server);
featureModeManagementService.createMode(featureFlag, mode, true);
}
}
@Override
public AFeatureMode setModeForFeatureTo(AFeature feature, AServer server, FeatureMode mode) {
AFeatureFlag featureFlag = featureFlagManagementService.getFeatureFlag(feature, server);
return setModeForFeatureTo(featureFlag, mode);
public void setFutureModeForFuture(FeatureEnum featureEnum, AServer server, FeatureMode mode, Boolean newValue) {
if(newValue) {
enableFeatureModeForFeature(featureEnum, server, mode);
} else {
disableFeatureModeForFeature(featureEnum, server, mode);
}
}
@Override
public AFeatureMode setModeForFeatureTo(AFeatureFlag featureFlag, FeatureMode mode) {
return featureModeManagementService.setModeForFeature(featureFlag, mode);
}
@Override
public AFeatureMode createMode(String key, AServer server, String newMode) {
FeatureEnum featureEnum = featureConfigService.getFeatureEnum(key);
return createMode(featureEnum, server, newMode);
}
@Override
public AFeatureMode createMode(AFeatureFlag flag, String newMode) {
FeatureConfig featureConfig = featureConfigService.getFeatureDisplayForFeature(flag.getFeature().getKey());
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(featureConfig, newMode);
return createMode(flag, featureMode);
}
@Override
public AFeatureMode createMode(FeatureEnum featureEnum, AServer server, String newMode) {
FeatureConfig featureConfig = featureConfigService.getFeatureDisplayForFeature(featureEnum);
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(featureConfig, newMode);
return createMode(featureEnum, server, featureMode);
}
@Override
public AFeatureMode createMode(AFeature feature, AServer server, String newMode) {
FeatureConfig featureConfig = featureConfigService.getFeatureDisplayForFeature(feature.getKey());
FeatureMode featureMode = featureConfigService.getFeatureModeByKey(featureConfig, newMode);
return createMode(feature, server, featureMode);
}
@Override
public AFeatureMode createMode(FeatureEnum featureEnum, AServer server, FeatureMode mode) {
public void disableFeatureModeForFeature(FeatureEnum featureEnum, AServer server, FeatureMode mode) {
AFeature feature = featureManagementService.getFeature(featureEnum.getKey());
return createMode(feature, server, mode);
Optional<AFeatureMode> existing = featureModeManagementService.getFeatureMode(feature, server, mode);
if(existing.isPresent()) {
existing.get().setEnabled(false);
} else {
AFeatureFlag featureFlag = featureFlagManagementService.getFeatureFlag(feature, server);
featureModeManagementService.createMode(featureFlag, mode, false);
}
}
@Override
public AFeatureMode createMode(AFeature feature, AServer server, FeatureMode mode) {
AFeatureFlag featureFlag = featureFlagManagementService.getFeatureFlag(feature, server);
return createMode(featureFlag, mode);
}
@Override
public AFeatureMode createMode(AFeatureFlag featureFlag, FeatureMode mode) {
return featureModeManagementService.createMode(featureFlag, mode);
}
@Override
public AFeatureMode getFeatureMode(FeatureEnum featureEnum, AServer server) {
public boolean featureModeActive(FeatureEnum featureEnum, AServer server, FeatureMode mode) {
AFeature feature = featureManagementService.getFeature(featureEnum.getKey());
return getFeatureMode(feature, server);
if(featureModeManagementService.doesFeatureModeExist(feature, server, mode)) {
return featureModeManagementService.isFeatureModeActive(feature, server, mode);
} else {
return defaultFeatureModeManagement.getFeatureMode(feature, mode.getKey()).isEnabled();
}
}
@Override
public AFeatureMode getFeatureMode(AFeature feature, AServer server) {
AFeatureFlag featureFlag = featureFlagManagementService.getFeatureFlag(feature, server);
return featureModeManagementService.getModeForFeature(featureFlag);
public FeatureMode getFeatureModeForKey(String key) {
return getAllAvailableFeatureModes().stream().filter(mode -> mode.getKey().equalsIgnoreCase(key)).findAny().orElseThrow(() -> new FeatureModeNotFoundException(key, getFeatureModesAsStrings()));
}
@Override
public List<FeatureMode> getAllAvailableFeatureModes() {
List<FeatureMode> featureModes = new ArrayList<>();
featureConfigService.getAllFeatureConfigs().forEach(featureConfig -> featureModes.addAll(featureConfig.getAvailableModes()));
return featureModes;
}
private List<String> getFeatureModesAsStrings() {
return getAllAvailableFeatureModes().stream().map(FeatureMode::getKey).collect(Collectors.toList());
}
@Override
public List<FeatureModeDisplay> getEffectiveFeatureModes(AServer server) {
List<DefaultFeatureMode> allDefaultModes = defaultFeatureModeManagement.getAll();
List<AFeatureMode> allModesFromServer = featureModeManagementService.getFeatureModesOfServer(server);
return combineFeatureModesWithDefault(server, allDefaultModes, allModesFromServer);
}
private List<FeatureModeDisplay> combineFeatureModesWithDefault(AServer server, List<DefaultFeatureMode> allDefaultModes, List<AFeatureMode> allModesFromServer) {
List<FeatureModeDisplay> result = new ArrayList<>();
List<AFeatureMode> activeModes = allModesFromServer.stream().filter(AFeatureMode::getEnabled).collect(Collectors.toList());
List<AFeatureMode> disabledModes = allModesFromServer.stream().filter(aFeatureMode -> !aFeatureMode.getEnabled()).collect(Collectors.toList());
List<String> usedModes = allModesFromServer.stream().map(aFeatureMode -> aFeatureMode.getFeatureMode().getMode()).collect(Collectors.toList());
HashMap<String, FeatureConfig> featureConfigCache = new HashMap<>();
Consumer<AFeatureMode> loadUsedValues = aFeatureMode -> {
FeatureConfig featureConfig = getFeatureConfig(featureConfigCache, aFeatureMode.getFeatureFlag().getFeature());
FeatureModeDisplay featureModeDisplay = FeatureModeDisplay
.builder()
.featureMode(aFeatureMode)
.isDefaultValue(false)
.featureConfig(featureConfig)
.build();
result.add(featureModeDisplay);
};
activeModes.forEach(loadUsedValues);
disabledModes.forEach(loadUsedValues);
allDefaultModes.forEach(defaultFeatureMode -> {
if(!usedModes.contains(defaultFeatureMode.getMode())) {
FeatureConfig featureConfig = getFeatureConfig(featureConfigCache, defaultFeatureMode.getFeature());
AFeatureFlag featureFlag = featureFlagManagementService.getFeatureFlag(defaultFeatureMode.getFeature(), server);
AFeatureMode fakeMode = AFeatureMode.builder().server(server).enabled(defaultFeatureMode.isEnabled()).featureMode(defaultFeatureMode).featureFlag(featureFlag).build();
FeatureModeDisplay featureModeDisplay = FeatureModeDisplay
.builder()
.featureMode(fakeMode)
.isDefaultValue(true)
.featureConfig(featureConfig)
.build();
result.add(featureModeDisplay);
}
});
return result;
}
private FeatureConfig getFeatureConfig(HashMap<String, FeatureConfig> featureConfigs, AFeature feature) {
String featureKey = feature.getKey();
FeatureConfig featureConfig;
if (featureConfigs.containsKey(featureKey)) {
featureConfig = featureConfigs.get(featureKey);
} else {
featureConfig = featureConfigService.getFeatureConfigForFeature(feature);
featureConfigs.put(featureKey, featureConfig);
}
return featureConfig;
}
@Override
public List<FeatureModeDisplay> getEffectiveFeatureModes(AServer server, AFeature feature) {
List<DefaultFeatureMode> allDefaultModes = defaultFeatureModeManagement.getFeatureModesForFeature(feature);
List<AFeatureMode> allModesFromServer = featureModeManagementService.getFeatureModesOfFeatureInServer(server, feature);
return combineFeatureModesWithDefault(server, allDefaultModes, allModesFromServer);
}

View File

@@ -0,0 +1,42 @@
package dev.sheldan.abstracto.core.service.management;
import dev.sheldan.abstracto.core.exception.FeatureModeNotFoundException;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.DefaultFeatureMode;
import dev.sheldan.abstracto.core.repository.DefaultFeatureModeRepository;
import dev.sheldan.abstracto.core.service.FeatureConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class DefaultFeatureModeManagementBean implements DefaultFeatureModeManagement {
@Autowired
private DefaultFeatureModeRepository defaultFeatureModeRepository;
@Autowired
private FeatureConfigService featureConfigService;
@Override
public List<DefaultFeatureMode> getFeatureModesForFeature(AFeature feature) {
return defaultFeatureModeRepository.findByFeature(feature);
}
@Override
public List<DefaultFeatureMode> getAll() {
return defaultFeatureModeRepository.findAll();
}
@Override
public Optional<DefaultFeatureMode> getFeatureModeOptional(AFeature feature, String mode) {
return defaultFeatureModeRepository.findByFeatureAndMode(feature, mode);
}
@Override
public DefaultFeatureMode getFeatureMode(AFeature feature, String mode) {
return getFeatureModeOptional(feature, mode).orElseThrow(() -> new FeatureModeNotFoundException(mode, featureConfigService.getFeatureModesFromFeatureAsString(feature.getKey())));
}
}

View File

@@ -50,6 +50,11 @@ public class FeatureFlagManagementServiceBean implements FeatureFlagManagementSe
return getFeatureFlag(feature, server);
}
@Override
public boolean featureFlagExists(AFeature feature, AServer server) {
return repository.existsByServerAndFeature(server, feature);
}
@Override
public AFeatureFlag getFeatureFlag(AFeature feature, AServer server) {
return repository.findByServerAndFeature(server, feature);

View File

@@ -1,26 +1,37 @@
package dev.sheldan.abstracto.core.service.management;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.models.database.AFeature;
import dev.sheldan.abstracto.core.models.database.AFeatureFlag;
import dev.sheldan.abstracto.core.models.database.AFeatureMode;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.*;
import dev.sheldan.abstracto.core.repository.FeatureModeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class FeatureModeManagementServiceBean implements FeatureModeManagementService {
@Autowired
private FeatureModeRepository featureModeRepository;
@Autowired
private DefaultFeatureModeManagement defaultFeatureModeManagement;
@Override
public AFeatureMode createMode(AFeatureFlag featureFlag, FeatureMode mode) {
public AFeatureMode createMode(AFeatureFlag featureFlag, FeatureMode mode, boolean enabled) {
return createMode(featureFlag, mode.getKey(), enabled);
}
@Override
public AFeatureMode createMode(AFeatureFlag featureFlag, String mode, boolean enabled) {
DefaultFeatureMode defaultMode = defaultFeatureModeManagement.getFeatureMode(featureFlag.getFeature(), mode);
AFeatureMode aFeatureMode = AFeatureMode
.builder()
.featureFlag(featureFlag)
.mode(mode.getKey())
.server(featureFlag.getServer())
.enabled(enabled)
.featureMode(defaultMode)
.build();
featureModeRepository.save(aFeatureMode);
@@ -28,19 +39,40 @@ public class FeatureModeManagementServiceBean implements FeatureModeManagementSe
}
@Override
public AFeatureMode getModeForFeature(AFeatureFlag featureFlag) {
return featureModeRepository.findByFeatureFlag(featureFlag);
public boolean isFeatureModeActive(AFeature aFeature, AServer server, FeatureMode mode) {
Optional<AFeatureMode> featureModeOptional = featureModeRepository.findByServerAndFeatureFlag_FeatureAndFeatureMode_Mode(server, aFeature, mode.getKey());
return featureModeOptional.isPresent() && featureModeOptional.get().getEnabled();
}
@Override
public boolean featureModeSet(AFeature aFeature, AServer server) {
return featureModeRepository.existsByFeatureFlag_ServerAndFeatureFlag_Feature(server, aFeature);
public boolean doesFeatureModeExist(AFeature aFeature, AServer server, FeatureMode mode) {
return getFeatureMode(aFeature, server, mode).isPresent();
}
@Override
public AFeatureMode setModeForFeature(AFeatureFlag featureFlag, FeatureMode featureMode) {
AFeatureMode modeForFeature = getModeForFeature(featureFlag);
modeForFeature.setMode(featureMode.getKey());
return modeForFeature;
public boolean doesFeatureModeExist(AFeature aFeature, AServer server, String modeKey) {
return featureModeRepository.findByServerAndFeatureFlag_FeatureAndFeatureMode_Mode(server, aFeature, modeKey).isPresent();
}
@Override
public Optional<AFeatureMode> getFeatureMode(AFeature aFeature, AServer server, FeatureMode mode) {
return getFeatureMode(aFeature, server, mode.getKey());
}
@Override
public Optional<AFeatureMode> getFeatureMode(AFeature aFeature, AServer server, String modeKey) {
return featureModeRepository.findByServerAndFeatureFlag_FeatureAndFeatureMode_Mode(server, aFeature, modeKey);
}
@Override
public List<AFeatureMode> getFeatureModesOfServer(AServer server) {
return featureModeRepository.findByServer(server);
}
@Override
public List<AFeatureMode> getFeatureModesOfFeatureInServer(AServer server, AFeature aFeature) {
return featureModeRepository.findByServerAndFeatureFlag_Feature(server, aFeature);
}
}

View File

@@ -170,7 +170,19 @@
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="setMode"/>
<column name="name" value="enableMode"/>
<column name="module_id" valueComputed="${configModule}"/>
<column name="feature_id" valueComputed="${coreFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="disableMode"/>
<column name="module_id" valueComputed="${configModule}"/>
<column name="feature_id" valueComputed="${coreFeature}"/>
<column name="created" valueComputed="${today}"/>
</insert>
<insert tableName="command">
<column name="name" value="featureModes"/>
<column name="module_id" valueComputed="${configModule}"/>
<column name="feature_id" valueComputed="${coreFeature}"/>
<column name="created" valueComputed="${today}"/>

View File

@@ -0,0 +1,28 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext ../../dbchangelog-3.8.xsd
http://www.liquibase.org/xml/ns/pro ../../dbchangelog-3.8.xsd" >
<changeSet author="Sheldan" id="default_feature_mode-table">
<createTable tableName="default_feature_mode">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true" primaryKeyName="default_feature_mode_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="mode" type="VARCHAR(255)"/>
<column name="enabled" type="BOOLEAN">
<constraints nullable="false"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="feature_id" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="Sheldan" id="default_feature_flag-fk_default_feature_mode_feature">
<addForeignKeyConstraint baseColumnNames="feature_id" baseTableName="default_feature_mode" constraintName="fk_default_feature_mode_feature" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="feature" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -12,8 +12,13 @@
<constraints nullable="false" primaryKey="true" primaryKeyName="feature_mode_pkey"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="mode" type="VARCHAR(255)"/>
<column name="enabled" type="BOOLEAN">
<constraints nullable="false"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="feature_mode_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="feature_flag_id" type="BIGINT">
<constraints nullable="false"/>
</column>
@@ -22,4 +27,7 @@
<changeSet author="Sheldan" id="feature_mode-fk_feature_mode_flag">
<addForeignKeyConstraint baseColumnNames="feature_flag_id" baseTableName="feature_mode" constraintName="fk_feature_mode_flag" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="feature_flag" validate="true"/>
</changeSet>
<changeSet author="Sheldan" id="feature_mode-fk_feature_mode_mode">
<addForeignKeyConstraint baseColumnNames="feature_mode_id" baseTableName="feature_mode" constraintName="fk_feature_mode_mode" deferrable="false" initiallyDeferred="false" onDelete="NO ACTION" onUpdate="NO ACTION" referencedColumnNames="id" referencedTableName="default_feature_mode" validate="true"/>
</changeSet>
</databaseChangeLog>

View File

@@ -19,6 +19,7 @@
<include file="command.xml" relativeToChangelogFile="true"/>
<include file="channel_group.xml" relativeToChangelogFile="true"/>
<include file="default_feature_flag.xml" relativeToChangelogFile="true"/>
<include file="default_feature_mode.xml" relativeToChangelogFile="true" />
<include file="feature_flag.xml" relativeToChangelogFile="true"/>
<include file="feature_mode.xml" relativeToChangelogFile="true"/>
<include file="lock.xml" relativeToChangelogFile="true"/>