Skip to content

Configuration System

Ture Bentzin edited this page Jan 21, 2023 · 13 revisions

The Core provides you with a centralized Configuration System to get rid of copying configs around the world on your server. This allows you to store configs that are synchronized over the whole network in one place. For this, I decided to use .properties as the only available format to allow storage of all kinds of data you might want to store. The API provides you with a whole system to read and modify these configs. It is technically possible to use these configs as a database but it is highly unrecommended. If you want to store data, then please use the JDBI API provided by the Core!`

How to use Configurations?

Location of the files

The Configurations are stored all in one place and are identified via a unique name. To locate the folder used to store the configurations, open the working directory of your Master. There should be a folder called "config", that by default contains two or one file. Among these files should be one that is called database.properties, this is your way to enter your database credentials. All your custom configurations will be saved here. If you copy a configuration in this folder, it will be loaded by the Master and distributed to all cores on the cluster. You can trigger the "loading" of the configs also manually by entering the command rlconfig into the Master terminal. In the case you would like to save the configs manually you can use svconfig - These commands are of course also available via the CoreCommand System - so it is possible to trigger these actions from every core connected to the cluster (and also from the web interface for advanced users and developers)

Creating your own Configuration

Easy way

The Configuration System is designed to create your configurations as soon as you try to load "get" them. To create your Configuration I suggest using the following method:

Configuration yourConfig = API.get().getConfigurationApi().getOrCreate("name_of_your_config");

Advanced way

It is also possible to enter a properties object here to provide "defaults" - this is for advanced users so I will only describe the basics of using this. To use the defaults, it is important to understand, that the behavior of this method changes regarding the existence of the config prior to the execution of this method.

The first thing you need to do is create your Properties and enter the configuration_name

final Properties defaults = new Properties();
defaults.setProperty("configuration_name","name_of_your_config");

There are currently two "keys" reserved for internal use in every config: configuration_name & configuration_header the configuration_header will be printed as a comment at the start of the file that is saved. Now you could decide to set the header or leave it at default.

You can also use the following method to do the same thing as explained above:

final Properties defaults = API.get().getConfigurationApi().initializeProperties("name_of_your_config", "my custom header");

After this necessary setup, you can enter your defaults like this:

        defaults.setProperty("test", "Value");
        defaults.setProperty("hu", "he");
        defaults.setProperty("Paper", "Brick");
        defaults.setProperty("Hard-fork", "no thanks!");

Please remember that it is only suggested to enter Strings here, you can always enter stuff after the creation of the config and use literally all datatypes you like! But all "String forms" of primitive datatypes are theoretically supported.

Now you are ready to create your Configuration out of our defaults:

        final Properties defaults = new Properties();
        defaults.setProperty("configuration_name","name_of_your_config");

        defaults.setProperty("test", "Value");
        defaults.setProperty("hu", "he");
        defaults.setProperty("Paper", "Brick");
        defaults.setProperty("Hard-fork", "no thanks!");

        Configuration defaultConfig = API.get().getConfigurationApi().getOrCreate(defaults);

Write data to your Configuration

If you have your Configuration Object, you can write data into it. The Configurations support all primitive Datatypes by default and can use an Interpreter to store and load all other Objects. They also support Iterables by default to store a "list" of data at once. Here are some examples of "easy-primitive" storing:

        //Saving a String
        yourConfig.setString("key_s","Hello World!");

        //Saving a Double
        yourConfig.setDouble("key_d", 10.3);
        
        //Saving an Integer
        yourConfig.setInteger("key_i",129);

If you want to store a custom Object in the Configuration you need to provide an Interpreter. The Task of an Interpreter is to serialize and deserialize the Object. If you want to store the "output" from your .toString() method and avoid implementing the serialization, then you can use the SimpleInterpreter Interface. This Interface also allows Objects that implement the CustomInterpretable. In this case, the .toInterpretableString() method is used and not the default .toString method! For this example, we want to store a URL to the Configuration (This interpreter is, because of the common usage provided by BuildInInterpreters, but for this example, we will rewrite it. For the URL we know that URL#toString provides a good representation of the URL, so we can use a SimpleInterpreter<URL> for this:

        Interpreter<URL> urlInterpreter = new SimpleInterpreter<URL>() {
            @Contract("_ -> new")
            @Override
            public @NotNull URL interpret(String input) throws Exception {
                return new URL(input);
            }
        };

This can be simplified using a lambda like this:

Interpreter<URL> urlInterpreter = (SimpleInterpreter<URL>) URL::new;

Now we can use this interpreter to store our URL to the configuration:

        //Saving our URL
        URL url = new URL("https://google.com");
        yourConfig.set("key_u", url, urlInterpreter);

Read from Configuration

Now we should be ready to read data back from the Configuration. The process to do this should be straight forward so I will only explain basic functionality here.

For example, if you want to read back the String we saved before you use this method:

@MaybePresent Optional<String> keyS = yourConfig.getString("key_s");

You can also take the risk and allow a null return if the key is not set:

@Nullable Double keyD = yourConfig.getDoubleOrNull("key_d");

Now let us take a look at loading our "custom" Object back. First, we will need to ensure that we have access to the Interpreter (so the formats are compatible), after this we need to decide if we want to take the risk of a null return or not. Here are the Examples for both options:

        @MaybePresent Optional<URL> key_u = yourConfig.get("key_u", urlInterpreter);
        @Nullable URL keyU = yourConfig.getOrNull("key_u", urlInterpreter);

Iterables (Collections)

Finally, let's take a look at saving and loading collections using the supported Iterables. The general idea of this system is, that you provide a keySpace and all the values of the Iterable will be stored like this

keySpace_0: <firstValue>
keySpace_1: <secondValue>
keySpace_2: <thirdValue>

Please note that the loading of this data is performed using a "starts with" call, so this KeySpace should be exclusive to this data and not used for other purposes. Also, it is not ensured that the order of the elements stays the same... please report any issues you encounter with this System here via GitHub Issues

Saving a Collection:

        List<String> integerList = List.of("hallo", "ä", "core", "config", "asduoip");
        yourConfig.setIterable("keySpace", integerList, BuildInInterpreters.interpreter());

In this case, I use the "interpreter" from BuildInInterpreters, which allows default Strings to be saved and loaded. And this Interpreter will be used to load the Collection again (a saved Iterable is always loaded as a collection):

        Collection<String> collection = yourConfig.getCollection("keySpace", BuildInInterpreters.interpreter());
        List<String> list = List.copyOf(collection);

This will also convert the Collection back to a list (if you need)