Spring PropertyResolver

0

In this article, we will discuss how spring parses and resolves a string with placeholders. We will look into the algorithm and all the related scenarios.

Example

Lets suppose "Buy ${${quantity} ${size}} ${container} of fresh ${${berries}}" is the string which needs to be parsed and resolved. If the placeholder values are:

Property resolver algorithm
Property resolver algorithm

The final parsed string would be “Buy 1 large basket of fresh Marionberries”.

Test case

In the below test case, we create the property sources and resolve the placeholders based on it. PropertySourcesPropertyResolver is the property resolver. resolver.resolveRequiredPlaceholders(string) parses the string passed in, resolves the property place holders and returns us the resolved one. We create the property sources using MutablePropertySources.

@Test
    public void resolveMultiplePlaceholders() {
        MutablePropertySources propertySources = new MutablePropertySources();
        PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
        propertySources.addLast(new MockPropertySource()
                .withProperty("berries", "${berriesType}Berries")
                .withProperty("berriesType", "black")
                .withProperty("blackBerries", "Marionberries")
                .withProperty("container", "basket")
                .withProperty("size", "large")
                .withProperty("quantity", "one")
                .withProperty("one large", "1 large"));
        assertThat(resolver.resolveRequiredPlaceholders("Buy ${${quantity} ${size}} ${container} of fresh ${${berries}}"),
                equalTo("Buy 1 large basket of fresh Marionberries"));
    }

Place Holder Scenarios

What are the scenarios involved?

  • A string might contain more than one place holder.
  • The place holder value itself can be composed of sub placeholders.
  • In case there is no place holder value but still there should be a way to resort to a default value.
  • In case the we don’t have a default value, we should either fail it or proceed with further parsing ignoring the unresolved placeholder.

Place Holder Resolver logic

The parsing logic and the mechanism used to resolve the placeholders is decoupled as there can be many ways to resolve a property. For example the property source might come from system environment, system properties, servlet etc.
In this article we will only concentrate on the parsing logic. Since there can be more than one place holder in a string and the resolved value itself can contain further placeholders, the parsing is done recursively.
Default values can be supplied using the “:” separator between key and value.

Below diagram contains the flow.

Property resolver algorithm
Property resolver algorithm

Based on the above mentioned example, where the string is "Buy ${${quantity} ${size}} ${container} of fresh ${${berries}}".
And the placeholder values:

Property resolver algorithm
Property resolver algorithm

Below diagram shows how each placeholder is parsed in sequence.

Property resolver algorithm
Property resolver algorithm

Placeholder’s default value

If a placeholder’s value is not specified, it can be set to a default value.
To test this, we modify our example. In the test below, “berries” is not added to the property sources. For the test to work, we should a default value to the placeholder “berries”.
This is done by using a separator. Instead of ${berries}, we now pass a default value to it using a separator “:” -> ${berries:blackberries}.>

    @Test
    public void defaultPlaceholderValue() {
        MutablePropertySources propertySources = new MutablePropertySources();
        PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
        propertySources.addLast(new MockPropertySource()
                .withProperty("berriesType", "black")
                .withProperty("blackBerries", "Marionberries")
                .withProperty("container", "Basket")
                .withProperty("size", "large")
                .withProperty("quantity", "one")
                .withProperty("one large", "1 large"));
        assertThat(resolver.resolveRequiredPlaceholders("Buy ${${quantity} ${size}} ${container} of fresh ${${berries:blackBerries}}"), equalTo("Buy 1 large Basket of fresh Marionberries"));
    }

The default value itself can be another placeholder. For example, instead of ${berries:blackBerries}, we now use ${berries:${blackBerries}} where the default value is ${blackBerries}. A new placeholder is been added for “Marionberries”-> “Marion blackberry, Orgeon”. The final parsed string would be "Buy 1 large Basket of fresh Marion blackberry, Orgeon".

    @Test
    public void defaultValueAsPlaceholder() {
        MutablePropertySources propertySources = new MutablePropertySources();
        PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
        propertySources.addLast(new MockPropertySource()
                .withProperty("berriesType", "black")
                .withProperty("blackBerries", "Marionberries")
                .withProperty("Marionberries", "Marion blackberry, Orgeon")
                .withProperty("container", "Basket")
                .withProperty("size", "large")
                .withProperty("quantity", "one")
                .withProperty("one large", "1 large"));
        assertThat(resolver.resolveRequiredPlaceholders("Buy ${${quantity} ${size}} ${container} of fresh ${${berries:${blackBerries}}}"), equalTo("Buy 1 large Basket of fresh Marion blackberry, Orgeon"));
    }

Missing placeholder value

By default, if a placeholder’s value is not specified, the test is going to fail with an IllegalArgumentException.

    @Test
    public void missingPlaceholderError() {
        MutablePropertySources propertySources = new MutablePropertySources();
        PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
        propertySources.addLast(new MockPropertySource()
                .withProperty("two", "2"));
        try {
            resolver.resolveRequiredPlaceholders("${one} and ${two}");
            fail("should have failed as placeholder value for "two" is missing");
        } catch (IllegalArgumentException e) {
            assertTrue(true);
        }
    }

We may want to ignore the missing placeholder and parse rest of the string instead of aborting the parsing, in such case, we should use resolvePlaceholders instead of the stricter version resolveRequiredPlaceholders

    @Test
    public void ignoreMissingPlaceholder() {
        MutablePropertySources propertySources = new MutablePropertySources();
        PropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
        propertySources.addLast(new MockPropertySource()
                .withProperty("two", "2"));
        assertThat(resolver.resolvePlaceholders("${one} and ${two}"), equalTo("${one} and 2"));
    }
Share.

Leave A Reply