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