1

I am trying to setup a custom @ConfigurationProperties class loaded from a HOCON syntax .conf file.

I have a Class annotated with @PropertySource(factory=TypesafePropertySourceFactory.class, value = "classpath:app.conf")

@Configuration
@ConfigurationProperties(value = "app.server")
@PropertySource(factory = TypesafePropertySourceFactory.class, value = "classpath:app.conf")
public class ServerProperties {
    public int port;
}

and a simple test class:

@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeTest {
    @Test
    public void someCoolTest() {/* ... */}
    // ...
}

When i run my junit test runner, i get the following error:

Caused by: com.typesafe.config.ConfigException$BadPath: path parameter: Invalid path 'spring.info.build.location:classpath:META-INF/build-info.properties': Token not allowed in path expression: ':' (you can double-quote this token if you really want it here)
    at com.typesafe.config.impl.PathParser.parsePathExpression(PathParser.java:155) ~[config-1.4.0.jar:1.4.0]
    at com.typesafe.config.impl.PathParser.parsePathExpression(PathParser.java:74) ~[config-1.4.0.jar:1.4.0]
    at com.typesafe.config.impl.PathParser.parsePath(PathParser.java:61) ~[config-1.4.0.jar:1.4.0]
...

If i uncomment the @PropertySource line on the ServerProperties class, the tests proceed normally. It seems strange to me that my custom PropertySourceFactory gets in the way of the default .properties file resolution process.

PropertySource and Factory classes

//     TypesafeConfigPropertySource.java
import com.typesafe.config.Config;
import org.springframework.core.env.PropertySource;

public class TypesafeConfigPropertySource extends PropertySource<Config> {
    public TypesafeConfigPropertySource(String name, Config source) {
        super(name, source);
    }

    @Override
    public Object getProperty(String path) {
        if (source.hasPath(path)) {
            return source.getAnyRef(path);
        }
        return null;
    }
}

//     TypesafePropertySourceFactory.java
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;

import java.io.IOException;
import java.util.Objects;

public class TypesafePropertySourceFactory implements PropertySourceFactory {

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        Config config = ConfigFactory.load(Objects.requireNonNull(resource.getResource().getFilename())).resolve();

        String safeName = name == null ? "typeSafe" : name;
        return new TypesafeConfigPropertySource(safeName, config);
    }

}

Am I missing something fundamental about configuring custom property resource factories, or is this a bug?

Versions

  • Spring boot 2.3.4
  • Junit Jupiter 5.6.2
coderatchet
  • 8,120
  • 17
  • 69
  • 125

2 Answers2

0

Maybe you can also solve it with the use of a ContextInitializer as suggested in the answer here:

Spring Environment backed by Typesafe Config

anurag saxena
  • 277
  • 1
  • 3
  • 10
0

TL;DR

Return null if you cannot process the path in your custom impl

public class TypesafeConfigPropertySource extends PropertySource<Config> {
    // ...    
    @Override
    public Object getProperty(String path) {
        try {
            if (source.hasPath(path)) {
                return source.getAnyRef(path);
            }
        } catch(ConfigException.BadPath ignore) {
        }
        return null;
    }
    // ...
}

Explanation

I am making educated guesses, but functionally this appears supported by the way the code behaves

the most likely scenario here is the resolution order will consider our custom implementation before any default implementation. The method used in our implementation will error out with any path containing a ":" and "[" as the error occurs in the check for the path's existence.

I'm simply wrapping the BadPath exception in order to catch any problem and then returning null to signify no match.

coderatchet
  • 8,120
  • 17
  • 69
  • 125