I have a bunch of entities, I would like to load from the application.yaml file. The structure seems rather complex but it is as it is and that includes polymorphism with some classes:
This is the base structor for the properties I want to load:
@Configuration
@ConfigurationProperties(prefix = "groups")
@Data
public class GroupsServiceProperties {
private List<GroupProperties> groups;
private List<AggregateProperties> aggregates;
}
@Data
public class GroupProperties {
private String id;
private Map<String, String> labels;
private SelectorProperties selector;
}
@Data
public class AggregateProperties {
private String id;
private String type;
private Map<String, String> labels;
private SelectorProperties selector;
}
And this is where the interesting part is:
@Data
public class SelectorProperties {
private String type;
}
@Data
@EqualsAndHashCode(callSuper = false)
public class LabelSelectorProperties extends SelectorProperties {
private String name;
private Operator operator;
private String value;
}
@Data
@EqualsAndHashCode(callSuper = false)
public class CompositeSelectorProperties extends SelectorProperties {
private Operator operator;
private List<SelectorProperties> selectors;
}
Now let me give some example of YAML that I want to load:
groups:
groups:
- id: office
labels:
name: Office
selector:
type: LabelSelector
name: room
operator: EQUALS
value: office
- id: all_power
labels:
name: Power
selector:
type: CompositeSelector
operator: AND
selectors:
- type: LabelSelector
name: property
operator: EQUALS
value: power
aggregates:
- id: indoor_temperature
type: QuantityAggregate
labels:
name: Indoor Temperature
selector:
type: CompositeSelector
operator: AND
selectors:
- type: LabelSelector
name: area
operator: EQUALS
value: indoor
- type: LabelSelector
name: device
operator: EQUALS
value: sensor
- type: LabelSelector
name: property
operator: EQUALS
value: temperature
So there are two GroupProperties in groups.groups and one AggregateProperties in groups.aggregates.
The first GroupProperties works, since it is referencing a LabelSelectorProperties directly. The LabelSelectorProperties instance has all the values.
The second GroupProperties does NOT work. What I get is an instance of CompositeSelector with a SelectorProperties instead of LabelSelectorProperties.
The same is the case for the AggregateProperties in the list.
So it looks like, my BindHandler is working unless when it comes to lists. Is that possible?
@Component
public class CustomBindHandlerAdvisor implements ConfigurationPropertiesBindHandlerAdvisor {
@Override
public BindHandler apply(BindHandler bindHandler) {
return new CustomBindHandler(bindHandler);
}
}
public class CustomBindHandler extends AbstractBindHandler {
private static final Logger logger = LoggerFactory.getLogger(CustomBindHandler.class);
private final Map<String, Class<? extends SelectorProperties>> configClasses = new HashMap<>();
public CustomBindHandler(BindHandler parent) {
super(parent);
configClasses.put("LabelSelector", LabelSelectorProperties.class);
configClasses.put("CompositeSelector", CompositeSelectorProperties.class);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
if (result instanceof SelectorProperties) {
SelectorProperties config = ((SelectorProperties) result);
if (configClasses.containsKey(config.getType())) {
Class<? extends SelectorProperties> aClass = configClasses.get(config.getType());
BindResult<? extends SelectorProperties> r = context.getBinder().bind(name, Bindable.of(aClass));
return r.get();
} else {
logger.error("unable to bind type {}", config.getType());
}
}
return super.onSuccess(name, target, context, result);
}
}
I added a System.out to the onSuccess method of the CustomBindHandler to see which properties it is actually consulted for. It never gets asked for groups.aggregates[0].selector.selectors[0] which is what I would expect.
groups.aggregates[0].id
groups.aggregates[0].labels.name
groups.aggregates[0].labels
groups.aggregates[0].selector.type
groups.aggregates[0].selector
groups.aggregates[0].type
groups.aggregates[0]
groups.aggregates
groups.groups[0].id
groups.groups[0].labels.name
groups.groups[0].labels
groups.groups[0].selector.type
groups.groups[0].selector
groups.groups[0]
groups.groups[1].id
groups.groups[1].labels.name
groups.groups[1].labels
groups.groups[1].selector.type
groups.groups[1].selector
groups.groups[1]
groups.groups[2].id
groups.groups[2].labels.name
groups.groups[2].labels
groups.groups[2].selector.type
groups.groups[2].selector
groups.groups[2]
groups.groups[3].id
groups.groups[3].labels.name
groups.groups[3].labels
groups.groups[3].selector.type
groups.groups[3].selector
groups.groups[3]
groups.groups
groups
What do I need to do to make this work for the list in CompositeSelector?
EDIT: I created a demo to illustrate the problem here: https://github.com/mathias-ewald/spring-configuration-properties-list-of-polymorphic-types