class AbstractPropertyMetricManager implements PropertyMetricManager {

    protected final Map<CacheKey, Mono<PropertyMetric>> cache = new ConcurrentHashMap<>();

    protected final ThingsRegistry registry;

    @Override
    public Mono<PropertyMetric> getPropertyMetric(ThingId thingId, String property, String metric) {

        return cache.computeIfAbsent(CacheKey.of(thingId, property, metric),
                                     key -> this
                                         .loadPropertyMetric(key.thingId, key.property, key.metric)
                                         .cache()
                                         .switchIfEmpty(loadFromTemplate(key.thingId, key.property, key.metric)));
    }

    private Mono<PropertyMetric> loadFromTemplate(ThingId thingId, String property, String metric) {<FILL_FUNCTION_BODY>}

    protected abstract Mono<PropertyMetric> loadPropertyMetric(ThingId thingId, String property, String metric);

    @Getter
    @Setter
    @EqualsAndHashCode
    @AllArgsConstructor(staticName = "of")
    public static class CacheKey implements Serializable {
        private static final long serialVersionUID = 1;

        private final ThingId thingId;
        private final String property;
        private final String metric;
    }
}

        return registry
            .getThing(thingId.getType(), thingId.getId())
            .flatMap(Thing::getTemplate)
            .flatMap(ThingTemplate::getMetadata)
            .flatMap(metadata -> Mono
                .justOrEmpty(
                    metadata
                        .getProperty(property)
                        .flatMap(propertyMetadata -> PropertyMetadataConstants.Metrics.getMetric(propertyMetadata, metric))
                )
            );
    
<no_super_class>