io.helidon.config.spi.AbstractConfigSource Maven / Gradle / Ivy
Show all versions of helidon-config Show documentation
/*
* Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.config.spi;
import java.io.StringReader;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Supplier;
import io.helidon.common.OptionalHelper;
import io.helidon.common.reactive.Flow;
import io.helidon.config.Config;
import io.helidon.config.internal.ConfigKeyImpl;
import io.helidon.config.internal.ListNodeBuilderImpl;
import io.helidon.config.internal.ObjectNodeBuilderImpl;
import io.helidon.config.spi.ConfigNode.ListNode;
import io.helidon.config.spi.ConfigNode.ObjectNode;
import io.helidon.config.spi.ConfigNode.ValueNode;
import io.helidon.config.spi.ConfigParser.Content;
/**
* Base abstract implementation of {@link ConfigSource}, suitable for concrete
* implementations to extend.
*
* @param a type of data stamp
* @see Builder
*/
public abstract class AbstractConfigSource extends AbstractSource implements ConfigSource {
private final Function mediaTypeMapping;
private final Function parserMapping;
private ConfigContext configContext;
/**
* Initializes config source from builder.
*
* @param builder builder to be initialized from
*/
protected AbstractConfigSource(Builder, ?> builder) {
super(builder);
mediaTypeMapping = builder.getMediaTypeMapping();
parserMapping = builder.getParserMapping();
}
@Override
public final void init(ConfigContext context) {
configContext = context;
}
protected ConfigContext getConfigContext() {
return configContext;
}
@Override
protected Data processLoadedData(Data data) {
if (!data.data().isPresent()
|| (mediaTypeMapping == null && parserMapping == null)) {
return data;
}
return new Data<>(Optional.of(processObject(data.stamp(), ConfigKeyImpl.of(), data.data().get())), data.stamp());
}
private ConfigNode processNode(Optional datastamp, ConfigKeyImpl key, ConfigNode node) {
switch (node.getNodeType()) {
case OBJECT:
return processObject(datastamp, key, (ObjectNode) node);
case LIST:
return processList(datastamp, key, (ListNode) node);
case VALUE:
return processValue(datastamp, key, (ValueNode) node);
default:
throw new IllegalArgumentException("Unsupported node type: " + node.getClass().getName());
}
}
private ObjectNode processObject(Optional datastamp, ConfigKeyImpl key, ObjectNode objectNode) {
ObjectNodeBuilderImpl builder = (ObjectNodeBuilderImpl) ObjectNode.builder();
objectNode.forEach((name, node) -> builder.addNode(name, processNode(datastamp, key.child(name), node)));
return builder.build();
}
private ListNode processList(Optional datastamp, ConfigKeyImpl key, ListNode listNode) {
ListNodeBuilderImpl builder = (ListNodeBuilderImpl) ListNode.builder();
for (int i = 0; i < listNode.size(); i++) {
builder.addNode(processNode(datastamp, key.child(Integer.toString(i)), listNode.get(i)));
}
return builder.build();
}
private ConfigNode processValue(Optional datastamp, Config.Key key, ValueNode valueNode) {
AtomicReference result = new AtomicReference<>(valueNode);
findParserForKey(key)
.ifPresent(parser -> result.set(parser.parse(
Content.from(new StringReader(valueNode.get()), null, datastamp))));
return result.get();
}
private Optional findParserForKey(Config.Key key) {
return OptionalHelper.from(Optional.ofNullable(parserMapping).map(mapping -> mapping.apply(key)))
.or(() -> Optional.ofNullable(mediaTypeMapping).map(mapping -> mapping.apply(key))
.flatMap(mediaType -> getConfigContext().findParser(mediaType)))
.asOptional();
}
/**
* {@inheritDoc}
*
* All subscribers are notified using specified {@link AbstractSource.Builder#changesExecutor(Executor) executor}.
*/
@Override
public final Flow.Publisher> changes() {
return getChangesPublisher();
}
/**
* A common {@link ConfigSource} builder ready to be extended by builder implementation related to {@link ConfigSource}
* extensions.
*
* It allows to configure following properties:
*
* - {@code mediaTypeMapping} - a mapping of a key to a media type
* - {@code parserMapping} - a mapping of a key to a {@link ConfigParser}
*
*
* @param type of Builder implementation
* @param type of key source attributes (target) used to construct polling strategy from
*/
public abstract static class Builder, T>
extends AbstractSource.Builder
implements Supplier {
private static final String MEDIA_TYPE_MAPPING_KEY = "media-type-mapping";
private final B thisBuilder;
private Function mediaTypeMapping;
private Function parserMapping;
private volatile ConfigSource configSource;
/**
* Initialize builder.
*
* @param targetType target type
*/
protected Builder(Class targetType) {
super(targetType);
this.thisBuilder = (B) this;
mediaTypeMapping = null;
parserMapping = null;
}
@Override
public ConfigSource get() {
if (configSource == null) {
configSource = build();
}
return configSource;
}
/**
* {@inheritDoc}
*
* - {@code media-type-mapping} - type {@code Map} - key to media type, see {@link #mediaTypeMapping(Function)}
*
*
* @param metaConfig configuration properties used to initialize a builder instance.
* @return modified builder instance
*/
@Override
protected B init(Config metaConfig) {
//media-type-mapping
metaConfig.get(MEDIA_TYPE_MAPPING_KEY).detach().asOptionalMap()
.ifPresent(this::initMediaTypeMapping);
return super.init(metaConfig);
}
private void initMediaTypeMapping(Map mediaTypeMapping) {
mediaTypeMapping(key -> mediaTypeMapping.get(key.toString()));
}
/**
* Sets a function mapping key to media type.
*
* @param mediaTypeMapping a mapping function
* @return a modified builder
*/
public B mediaTypeMapping(Function mediaTypeMapping) {
Objects.requireNonNull(mediaTypeMapping, "mediaTypeMapping cannot be null");
this.mediaTypeMapping = mediaTypeMapping;
return thisBuilder;
}
/**
* Sets a function mapping key to a parser.
*
* @param parserMapping a mapping function
* @return a modified builder
*/
public B parserMapping(Function parserMapping) {
Objects.requireNonNull(parserMapping, "parserMapping cannot be null");
this.parserMapping = parserMapping;
return thisBuilder;
}
protected Function getMediaTypeMapping() {
return mediaTypeMapping;
}
protected Function getParserMapping() {
return parserMapping;
}
}
}