org.apache.drill.exec.store.dfs.FormatPluginOptionsDescriptor.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.drill.exec.store.dfs.FormatPluginOptionsDescriptor.java

Source

/**
 * 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 org.apache.drill.exec.store.dfs;

import static java.util.Collections.unmodifiableMap;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.drill.common.exceptions.UserException;
import org.apache.drill.common.logical.FormatPluginConfig;
import org.apache.drill.exec.store.dfs.WorkspaceSchemaFactory.TableInstance;
import org.apache.drill.exec.store.dfs.WorkspaceSchemaFactory.TableParamDef;
import org.apache.drill.exec.store.dfs.WorkspaceSchemaFactory.TableSignature;
import org.slf4j.Logger;

import com.fasterxml.jackson.annotation.JsonTypeName;

/**
 * Describes the options for a format plugin
 * extracted from the FormatPluginConfig subclass
 */
final class FormatPluginOptionsDescriptor {
    private static final Logger logger = org.slf4j.LoggerFactory.getLogger(FormatPluginOptionsDescriptor.class);

    final Class<? extends FormatPluginConfig> pluginConfigClass;
    final String typeName;
    private final Map<String, TableParamDef> functionParamsByName;

    /**
     * Uses reflection to extract options based on the fields of the provided config class
     * ("List extensions" field is ignored, pending removal, Char is turned into String)
     * The class must be annotated with {@code @JsonTypeName("type name")}
     * @param pluginConfigClass the config class we want to extract options from through reflection
     */
    FormatPluginOptionsDescriptor(Class<? extends FormatPluginConfig> pluginConfigClass) {
        this.pluginConfigClass = pluginConfigClass;
        Map<String, TableParamDef> paramsByName = new LinkedHashMap<>();
        Field[] fields = pluginConfigClass.getDeclaredFields();
        // @JsonTypeName("text")
        JsonTypeName annotation = pluginConfigClass.getAnnotation(JsonTypeName.class);
        this.typeName = annotation != null ? annotation.value() : null;
        if (this.typeName != null) {
            paramsByName.put("type", new TableParamDef("type", String.class));
        }
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())
                    // we want to deprecate this field
                    || (field.getName().equals("extensions") && field.getType() == List.class)) {
                continue;
            }
            Class<?> fieldType = field.getType();
            if (fieldType == char.class) {
                // calcite does not like char type. Just use String and enforce later that length == 1
                fieldType = String.class;
            }
            paramsByName.put(field.getName(), new TableParamDef(field.getName(), fieldType).optional());
        }
        this.functionParamsByName = unmodifiableMap(paramsByName);
    }

    /**
     * returns the table function signature for this format plugin config class
     * @param tableName the table for which we want a table function signature
     * @return the signature
     */
    TableSignature getTableSignature(String tableName) {
        return new TableSignature(tableName, params());
    }

    /**
     * @return the parameters extracted from the provided format plugin config class
     */
    private List<TableParamDef> params() {
        return new ArrayList<>(functionParamsByName.values());
    }

    /**
     * @return a readable String of the parameters and their names
     */
    String presentParams() {
        StringBuilder sb = new StringBuilder("(");
        List<TableParamDef> params = params();
        for (int i = 0; i < params.size(); i++) {
            TableParamDef paramDef = params.get(i);
            if (i != 0) {
                sb.append(", ");
            }
            sb.append(paramDef.name).append(": ").append(paramDef.type.getSimpleName());
        }
        sb.append(")");
        return sb.toString();
    }

    /**
     * creates an instance of the FormatPluginConfig based on the passed parameters
     * @param t the signature and the parameters passed to the table function
     * @return the corresponding config
     */
    FormatPluginConfig createConfigForTable(TableInstance t) {
        // Per the constructor, the first param is always "type"
        TableParamDef typeParamDef = t.sig.params.get(0);
        Object typeParam = t.params.get(0);
        if (!typeParamDef.name.equals("type") || typeParamDef.type != String.class || !(typeParam instanceof String)
                || !typeName.equalsIgnoreCase((String) typeParam)) {
            // if we reach here, there's a bug as all signatures generated start with a type parameter
            throw UserException.parseError()
                    .message("This function signature is not supported: %s\n" + "expecting %s", t.presentParams(),
                            this.presentParams())
                    .addContext("table", t.sig.name).build(logger);
        }
        FormatPluginConfig config;
        try {
            config = pluginConfigClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw UserException
                    .parseError(e).message("configuration for format of type %s can not be created (class: %s)",
                            this.typeName, pluginConfigClass.getName())
                    .addContext("table", t.sig.name).build(logger);
        }
        for (int i = 1; i < t.params.size(); i++) {
            Object param = t.params.get(i);
            if (param == null) {
                // when null is passed, we leave the default defined in the config class
                continue;
            }
            if (param instanceof String) {
                // normalize Java literals, ex: \t, \n, \r
                param = StringEscapeUtils.unescapeJava((String) param);
            }
            TableParamDef paramDef = t.sig.params.get(i);
            TableParamDef expectedParamDef = this.functionParamsByName.get(paramDef.name);
            if (expectedParamDef == null || expectedParamDef.type != paramDef.type) {
                throw UserException.parseError()
                        .message("The parameters provided are not applicable to the type specified:\n"
                                + "provided: %s\nexpected: %s", t.presentParams(), this.presentParams())
                        .addContext("table", t.sig.name).build(logger);
            }
            try {
                Field field = pluginConfigClass.getField(paramDef.name);
                field.setAccessible(true);
                if (field.getType() == char.class && param instanceof String) {
                    String stringParam = (String) param;
                    if (stringParam.length() != 1) {
                        throw UserException.parseError()
                                .message("Expected single character but was String: %s", stringParam)
                                .addContext("table", t.sig.name).addContext("parameter", paramDef.name)
                                .build(logger);
                    }
                    param = stringParam.charAt(0);
                }
                field.set(config, param);
            } catch (IllegalAccessException | NoSuchFieldException | SecurityException e) {
                throw UserException.parseError(e)
                        .message("can not set value %s to parameter %s: %s", param, paramDef.name, paramDef.type)
                        .addContext("table", t.sig.name).addContext("parameter", paramDef.name).build(logger);
            }
        }
        return config;
    }

    @Override
    public String toString() {
        return "OptionsDescriptor [pluginConfigClass=" + pluginConfigClass + ", typeName=" + typeName
                + ", functionParamsByName=" + functionParamsByName + "]";
    }
}