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

io.streamthoughts.kafka.connect.filepulse.config.SourceTaskConfig Maven / Gradle / Ivy

/*
 * Copyright 2019-2020 StreamThoughts.
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.streamthoughts.kafka.connect.filepulse.config;

import io.streamthoughts.kafka.connect.filepulse.filter.RecordFilter;
import io.streamthoughts.kafka.connect.filepulse.fs.DefaultTaskFileURIProvider;
import io.streamthoughts.kafka.connect.filepulse.reader.FileInputReader;
import io.streamthoughts.kafka.connect.filepulse.fs.TaskFileURIProvider;
import org.apache.kafka.common.config.AbstractConfig;
import org.apache.kafka.common.config.ConfigDef;
import org.apache.kafka.common.config.ConfigException;
import org.apache.kafka.connect.errors.ConnectException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 *
 */
public class SourceTaskConfig extends CommonSourceConfig {

    public static final String FILE_URIS_PROVIDER_CONFIG = "file.uris.provider";
    private static final String FILE_URIS_PROVIDER_DOC    = "The FileURIProvider class to be used for retrieving the file URIs to process.";

    private static final String OMIT_READ_COMMITTED_FILE_CONFIG = "ignore.committed.offsets";
    private static final String OMIT_READ_COMMITTED_FILE_DOC = "Should a task ignore committed offsets while scheduling a file (default : false).";

    public static final String TASK_GENERATION_ID = "task.generation.id";
    private static final String TASK_GENERATION_DOC = "The task configuration generation id.";

    private final EnrichedConnectorConfig enrichedConfig;

    static ConfigDef getConf() {
        return CommonSourceConfig.getConfigDev()
                .define(
                        FILE_URIS_PROVIDER_CONFIG,
                        ConfigDef.Type.CLASS,
                        DefaultTaskFileURIProvider.class,
                        ConfigDef.Importance.HIGH,
                        FILE_URIS_PROVIDER_DOC
                )
                .define(
                        OMIT_READ_COMMITTED_FILE_CONFIG,
                        ConfigDef.Type.BOOLEAN,
                        false,
                        ConfigDef.Importance.LOW,
                        OMIT_READ_COMMITTED_FILE_DOC
                )
                .define(
                        TASK_GENERATION_ID,
                        ConfigDef.Type.INT,
                        0,
                        ConfigDef.Importance.LOW,
                        TASK_GENERATION_DOC
                );
    }

    /**
     * Creates a new {@link SourceTaskConfig} instance.
     *
     * @param originals the original configs.
     */
    public SourceTaskConfig(final Map originals) {
        this(getConf(), originals);
    }

    /**
     * Creates a new {@link SourceTaskConfig} instance.
     *
     * @param configDef the configuration definition.
     * @param originals the original configs.
     */
    private SourceTaskConfig(final ConfigDef configDef, final Map originals) {
        super(getConf(), originals);
        enrichedConfig = new EnrichedConnectorConfig(
                enrich(configDef, originals),
                originals
        );
    }

    @Override
    public Object get(String key) {
        return enrichedConfig.get(key);
    }

    private static ConfigDef enrich(final ConfigDef baseConfigDef, final Map props) {
        Object filterAliases = ConfigDef.parseType(FILTER_CONFIG, props.get(FILTER_CONFIG), ConfigDef.Type.LIST);
        if (!(filterAliases instanceof List)) {
            return baseConfigDef;
        }

        final ConfigDef newDef = new ConfigDef(baseConfigDef);

        LinkedHashSet uniqueFilterAliases = new LinkedHashSet<>();
        lookupAllFilterAliases(props, (List) filterAliases, uniqueFilterAliases);

        uniqueFilterAliases.forEach(alias -> addConfigDefForFilter(props, newDef, alias));

        return newDef;
    }

    private static void lookupAllFilterAliases(final Map props,
                                               final Collection filterAliases,
                                               final LinkedHashSet accumulator) {
        for (Object alias : filterAliases) {
            if (!(alias instanceof String)) {
                throw new ConfigException("Item in " + filterAliases + " property is not of type string");
            }
            accumulator.add((String) alias);
            final String failure = props.get(FILTER_CONFIG + "." + alias + "." + CommonFilterConfig.ON_FAILURE_CONFIG);
            if (failure != null && !failure.isEmpty()) {
                final Set filters = Arrays.stream(failure
                        .split(","))
                        .map(String::trim)
                        .collect(Collectors.toSet());
                lookupAllFilterAliases(props, filters, accumulator);
            }
        }
    }

    private static void addConfigDefForFilter(final Map props,
                                              final ConfigDef newDef,
                                              final String alias) {
        final String prefix = FILTER_CONFIG + "." + alias + ".";
        final String group = FILTERS_GROUP + ":" + alias;
        int orderInGroup = 0;

        final String filterTypeConfig = prefix + "type";
        final ConfigDef.Validator typeValidator = (name, value) -> getConfigDefFromFilter(filterTypeConfig, (Class) value);
        newDef.define(filterTypeConfig,
                ConfigDef.Type.CLASS,
                ConfigDef.NO_DEFAULT_VALUE,
                typeValidator,
                ConfigDef.Importance.HIGH,
                "Class for the '" + alias + "' filter.",
                group,
                orderInGroup++,
                ConfigDef.Width.LONG,
                "Filter type for " + alias
        );

        final ConfigDef filterConfigDef;
        try {
            final String className = props.get(filterTypeConfig);
            final Class cls = (Class) ConfigDef.parseType(filterTypeConfig, className, ConfigDef.Type.CLASS);
            filterConfigDef = getConfigDefFromFilter(filterTypeConfig, cls);
        } catch (ConfigException e) {
            return;
        }
        newDef.embed(prefix, group, orderInGroup, filterConfigDef);
    }

    public TaskFileURIProvider getFileURIProvider() {
        return this.getConfiguredInstance(FILE_URIS_PROVIDER_CONFIG, TaskFileURIProvider.class);
    }

    public boolean isReadCommittedFile() {
        return this.getBoolean(OMIT_READ_COMMITTED_FILE_CONFIG);
    }

    public String topic() {
        return this.getString(CommonSourceConfig.OUTPUT_TOPIC_CONFIG);
    }

    public FileInputReader reader() {
        return getConfiguredInstance(CommonSourceConfig.TASKS_FILE_READER_CLASS_CONFIG, FileInputReader.class);
    }

    public int getTaskGenerationId() {
        return this.getInt(TASK_GENERATION_ID);
    }

    public List filters() {
        final List filterAliases = getList(FILTER_CONFIG);
        final List filters = new ArrayList<>(filterAliases.size());
        for (String alias : filterAliases) {
            filters.add(filterByAlias(alias));
        }
        return filters;
    }

    public RecordFilter filterByAlias(final String alias) {
        final String prefix = FILTER_CONFIG + "." + alias + ".";
        try {
            final RecordFilter filter = getClass(prefix + "type")
                    .asSubclass(RecordFilter.class)
                    .getDeclaredConstructor().newInstance();
            filter.configure(originalsWithPrefix(prefix), this::filterByAlias);
            return filter;
        } catch (Exception e) {
            throw new ConnectException("Failed to create filter with alias '" + alias + "'", e);
        }
    }

    /**
     * Return {@link ConfigDef} from {@code filterClass}, which is expected to be a non-null {@code Class},
     * by instantiating it and invoking {@link RecordFilter#configDef()}.
     */
    private static ConfigDef getConfigDefFromFilter(final String key, final Class filterClass) {
        if (filterClass == null || !RecordFilter.class.isAssignableFrom(filterClass)) {
            throw new ConfigException(key, String.valueOf(filterClass), "Not a RecordFilter");
        }
        RecordFilter filter;
        try {
            filter = filterClass.asSubclass(RecordFilter.class).getConstructor().newInstance();
        } catch (Exception e) {
            throw new ConfigException(key, String.valueOf(filterClass), "Error getting configDef definition from RecordFilter: " + e.getMessage());
        }
        ConfigDef configDef = filter.configDef();
        if (null == configDef) {
            throw new ConnectException(
                    String.format(
                            "%s.configDef() must return a ConfigDef that is not null.",
                            filterClass.getName()
                    )
            );
        }
        return configDef;
    }

    private static class EnrichedConnectorConfig extends AbstractConfig {

        /**
         * Creates a new {@link EnrichedConnectorConfig} instance.
         *
         * @param configDef the configuration definition.
         * @param props     the original configurations.
         */
        EnrichedConnectorConfig(final ConfigDef configDef,
                                final Map props) {
            super(configDef, props);
        }

        @Override
        public Object get(final String key) {
            return super.get(key);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy