All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.config.spi.AbstractConfigSource Maven / Gradle / Ivy

There is a newer version: 4.1.1
Show newest version
/*
 * 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; } } }