Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding ConfigurationProperties to Map of <Enum,Pojo>

Description I am trying to bind the following configuration with my Component class -

platform:
  service:
    config:
      guard:
       hostname: fancy-host1.kiki.com
       resources:
         - name: bark
           api-path: dog/alert/bark/{dog-id}
         - name: bite
           api-path: dog/alert/bite/{dog-id}
           json-path: $..kill-mode
      play:
        hostname: fancy-host2.kiki.com
        resources:
         - name: lick
           api-path: dog/chill/lick/{dog-id}
           json-path: $..cute-mode

My Component class looks something like this-

@Component
@ConfigurationProperties(prefix = "platform.service")
public class DogConfig
{
    @Getter
    @Setter
    public class Resource
    {
        private String name;
        private String apiPath;
        private String jsonPath;
    }

    @Getter
    @Setter
    public class APIConfig
    {
        private String hostname;
        private List<Resource> resources = new ArrayList<>();
    }

    private Map<ServiceType, APIConfig> config = new LinkedHashMap<>();

    public Map<ServiceType, APIConfig> getConfig()
    {
        return config;
    }

    public void setConfig(Map<ServiceType, APIConfig> config)
    {
        this.config = config;
    }
}

In the above code, ServiceType is an enum having values GUARD and PLAY.

Issue Though my spring boot application is not throwing any error on initializing, but it's not binding my YAML to DogConfig class. I'm not sure about what exactly I'm missing out here.

My troubleshooting effort so far I'm relying on this spring doc, to externalize my configurations. I know that @ConfigurationProperties are type safe and have individually tested binding of Enums, Maps and POJOs. But having all the three at once is something I'm not able to achieve.

like image 440
Akshay Singh Avatar asked Sep 27 '18 15:09

Akshay Singh


3 Answers

please add static in your inner class of Resource and APIConfig for example:

public static class Resource {
    private String name;
    private String apiPath;
    private String jsonPath;
}
like image 128
clevertension Avatar answered Oct 13 '22 20:10

clevertension


You can do this:

  1. Create a POJO like this:

    @Getter
    @Setter
    @ConfigurationProperties("platform.service")
    public class DogProperties {
    
        private Map<String, APIConfig> config;
    
    }
    
  2. In your DogConfig you can do this for get the propierties:

    @Configuration
    @EnableConfigurationProperties(DogProperties.class)
    public class DogConfig {
    
        @Autowire
        private DogProperties properties
    
        ...
    
        @Bean
        @Qualifier("guardConfig")
        public APIConfig guardConfig(){
           return properties.get("guard");
       }
    
     }
    

If you look this example, the secrete is a Map property, you can parse with a map with the key guard or play with APIConfig.

like image 37
Paco Avatar answered Oct 13 '22 21:10

Paco


EDITED:

You could bind the properties to the Constructor using the ConstructorBinding annotation (see https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-constructor-binding) like this to transform the string key to an enum key with the valueOf() method:

@Component
@ConfigurationProperties(prefix = "platform.service")
@ConstructorBinding
public class DogConfig {

    DogConfig(Map<String, APIConfig> config) {
       this.config = config.entrySet().stream().collect(
                        Collectors.toMap(
                           e -> ServiceType.valueOf(e.getKey()), 
                           Map.Entry::getValue
                        )
                );
    }

...

or this, by additionally using Jackson-Databind ObjectMapper to transform the wired map with additional map value type into one with pojo value type:

@Component
@ConfigurationProperties(prefix = "platform.service")
@ConstructorBinding
public class DogConfig {

    DogConfig(Map<ServiceType, Map<String,Object> > config) {
       this.config = config.entrySet().stream().collect(
                        Collectors.toMap(
                           Map.Entry::getKey,
                           e -> (new ObjectMapper()).convertValue(e.getValue(),APIConfig.class)
                        )
                );
    }

...

Like OP already hinted, a combination of both Enum and Pojo in a map does not seem to work. You may have to transform either of the two possible structures into the desired one.

like image 1
Tamim Avatar answered Oct 13 '22 21:10

Tamim