/**
* Copyright 2015 StreamSets Inc.
*
* Licensed under 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 com.streamsets.datacollector.creation;
import com.google.common.collect.ImmutableMap;
import com.streamsets.datacollector.config.ConfigDefinition;
import com.streamsets.datacollector.config.ModelType;
import com.streamsets.datacollector.config.PipelineConfiguration;
import com.streamsets.datacollector.config.PipelineGroups;
import com.streamsets.datacollector.config.PipelineWebhookConfig;
import com.streamsets.datacollector.config.RuleDefinitions;
import com.streamsets.datacollector.config.StageConfiguration;
import com.streamsets.datacollector.config.StageDefinition;
import com.streamsets.datacollector.config.StageLibraryDefinition;
import com.streamsets.datacollector.definition.ConfigValueExtractor;
import com.streamsets.datacollector.definition.StageDefinitionExtractor;
import com.streamsets.datacollector.stagelibrary.ClassLoaderReleaser;
import com.streamsets.datacollector.stagelibrary.StageLibraryTask;
import com.streamsets.datacollector.util.ElUtil;
import com.streamsets.datacollector.validation.Issue;
import com.streamsets.datacollector.validation.IssueCreator;
import com.streamsets.pipeline.api.Config;
import com.streamsets.pipeline.api.ConfigDef;
import com.streamsets.pipeline.api.ConfigDefBean;
import com.streamsets.pipeline.api.ExecutionMode;
import com.streamsets.pipeline.api.Stage;
import com.streamsets.pipeline.api.el.ELEvalException;
import com.streamsets.pipeline.api.impl.Utils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public abstract class PipelineBeanCreator {
private static final Logger LOG = LoggerFactory.getLogger(PipelineBeanCreator.class);
public static final String PIPELINE_LIB_DEFINITION = "Pipeline";
private static final String RULE_DEFINITIONS_LIB_DEFINITION = "RuleDefinitions";
private static final String PARAMETERS = "constants";
private static final PipelineBeanCreator CREATOR = new PipelineBeanCreator() {
};
public static PipelineBeanCreator get() {
return CREATOR;
}
public static final StageDefinition PIPELINE_DEFINITION = getPipelineDefinition();
static private StageDefinition getPipelineDefinition() {
StageLibraryDefinition libraryDef = new StageLibraryDefinition(
Thread.currentThread().getContextClassLoader(),
PIPELINE_LIB_DEFINITION,
PIPELINE_LIB_DEFINITION,
new Properties(),
null,
null,
null
);
return StageDefinitionExtractor.get().extract(libraryDef, PipelineConfigBean.class, "Pipeline Config Definitions");
}
private static final StageDefinition RULES_DEFINITION = getRulesDefinition();
static private StageDefinition getRulesDefinition() {
StageLibraryDefinition libraryDef = new StageLibraryDefinition(
Thread.currentThread().getContextClassLoader(),
RULE_DEFINITIONS_LIB_DEFINITION,
RULE_DEFINITIONS_LIB_DEFINITION,
new Properties(),
null,
null,
null
);
return StageDefinitionExtractor.get().extract(
libraryDef,
RuleDefinitionsConfigBean.class,
"Rules Definitions Config Definitions"
);
}
public PipelineConfigBean create(PipelineConfiguration pipelineConf, List<Issue> errors) {
int priorErrors = errors.size();
PipelineConfigBean pipelineConfigBean = createPipelineConfigs(pipelineConf, errors);
return (errors.size() == priorErrors) ? pipelineConfigBean : null;
}
public RuleDefinitionsConfigBean createRuleDefinitionsConfigBean(
RuleDefinitions ruleDefinitions,
List<Issue> errors
) {
RuleDefinitionsConfigBean ruleDefinitionsConfigBean = new RuleDefinitionsConfigBean();
if (createConfigBeans(ruleDefinitionsConfigBean, "", RULES_DEFINITION, "pipeline", errors)) {
injectConfigs(
ruleDefinitionsConfigBean,
"", RULES_DEFINITION.getConfigDefinitionsMap(),
RULES_DEFINITION,
getRulesConfAsStageConf(ruleDefinitions),
Collections.emptyMap(),
errors
);
}
return ruleDefinitionsConfigBean;
}
public PipelineBean create(
boolean forExecution,
StageLibraryTask library,
PipelineConfiguration pipelineConf,
List<Issue> errors
) {
return create(forExecution, library, pipelineConf, errors, null);
}
/**
* Create PipelineBean which means instantiating all stages for the pipeline.
*
* For multi-threaded pipelines this method will *NOT* create all required instances since the number of required
* instances is not known at the time of creation - for that the origin has to be initialized which is not at this
* point. Hence this method will create only one instance of the whole pipeline and it's up to the caller to call
* createPipelineStageBeans to instantiate remaining source-less pipelines later in the execution.
*/
public PipelineBean create(
boolean forExecution,
StageLibraryTask library,
PipelineConfiguration pipelineConf,
List<Issue> errors,
Map<String, Object> runtimeParameters
) {
int priorErrors = errors.size();
PipelineConfigBean pipelineConfigBean = create(pipelineConf, errors);
StageBean errorStageBean = null;
StageBean statsStageBean = null;
StageBean origin = null;
PipelineStageBeans stages = null;
if (pipelineConfigBean != null && pipelineConfigBean.constants != null) {
// Merge constant and runtime Constants
Map<String, Object> resolvedConstants = pipelineConfigBean.constants;
if (runtimeParameters != null) {
for (String key: runtimeParameters.keySet()) {
if (resolvedConstants.containsKey(key)) {
resolvedConstants.put(key, runtimeParameters.get(key));
}
}
}
// Instantiate usual stages
if(!pipelineConf.getStages().isEmpty()) {
origin = createStageBean(
forExecution,
library,
pipelineConf.getStages().get(0),
false,
resolvedConstants,
errors
);
stages = createPipelineStageBeans(
forExecution,
library,
pipelineConf.getStages().subList(1, pipelineConf.getStages().size()),
resolvedConstants,
errors
);
}
// It is not mandatory to have a stats aggregating target configured
StageConfiguration statsStageConf = pipelineConf.getStatsAggregatorStage();
if (statsStageConf != null) {
statsStageBean = createStageBean(
forExecution,
library,
statsStageConf,
false,
resolvedConstants,
errors
);
}
// Error stage is mandatory
StageConfiguration errorStageConf = pipelineConf.getErrorStage();
if (errorStageConf != null) {
errorStageBean = createStageBean(
forExecution,
library,
errorStageConf,
true,
resolvedConstants,
errors
);
} else {
errors.add(IssueCreator.getPipeline().create(
PipelineGroups.BAD_RECORDS.name(),
"badRecordsHandling",
CreationError.CREATION_009
));
}
// Validate Webhook Configs
if (pipelineConfigBean.webhookConfigs != null && !pipelineConfigBean.webhookConfigs.isEmpty()) {
int index = 0;
for (PipelineWebhookConfig webhookConfig: pipelineConfigBean.webhookConfigs) {
if (StringUtils.isEmpty(webhookConfig.webhookUrl)) {
Issue issue = IssueCreator.getPipeline().create(
PipelineGroups.NOTIFICATIONS.name(),
"webhookUrl",
CreationError.CREATION_080
);
issue.setAdditionalInfo("index", index);
errors.add(issue);
break;
}
index++;
}
}
}
// Something went wrong
if(errors.size() != priorErrors) {
return null;
}
return new PipelineBean(pipelineConfigBean, origin, stages, errorStageBean, statsStageBean);
}
private PipelineStageBeans createPipelineStageBeans(
boolean forExecution,
StageLibraryTask library,
List<StageConfiguration> stageConfigurations,
Map<String, Object> constants,
List<Issue> errors
) {
List<StageBean> stageBeans = new ArrayList<>(stageConfigurations.size());
for (StageConfiguration stageConf : stageConfigurations) {
StageBean stageBean = createStageBean(forExecution, library, stageConf, false, constants, errors);
if (stageBean != null) {
stageBeans.add(stageBean);
}
}
return new PipelineStageBeans(stageBeans);
}
/**
* Creates additional PipelineStageBeans for additional runners. Stages will share stage definition and thus
* class loader with the first given runner. That includes stages with private class loader as well.
*
* @param pipelineStageBeans First runner that should be duplicated.
* @param constants Pipeline constants
* @param errors Any generated errors will be stored in this list
*
* @return PipelineStageBeans with new instances of the given stages
*/
public PipelineStageBeans duplicatePipelineStageBeans(
PipelineStageBeans pipelineStageBeans,
Map<String, Object> constants,
List<Issue> errors
) {
List<StageBean> stageBeans = new ArrayList<>(pipelineStageBeans.size());
for(StageBean original: pipelineStageBeans.getStages()) {
StageBean stageBean = createStage(original.getDefinition(), ClassLoaderReleaser.NOOP_RELEASER, original.getConfiguration(), constants, errors);
if (stageBean != null) {
stageBeans.add(stageBean);
}
}
return new PipelineStageBeans(stageBeans);
}
public ExecutionMode getExecutionMode(PipelineConfiguration pipelineConf, List<Issue> errors) {
ExecutionMode mode = null;
String value = null;
if (pipelineConf.getConfiguration("executionMode") != null) {
if (pipelineConf.getConfiguration("executionMode").getValue() != null) {
value = pipelineConf.getConfiguration("executionMode").getValue().toString();
}
}
if (value != null) {
try {
mode = ExecutionMode.valueOf(value);
} catch (IllegalArgumentException ex) {
errors.add(IssueCreator.getPipeline().create("", "executionMode", CreationError.CREATION_070, value));
}
} else {
errors.add(IssueCreator.getPipeline().create("", "executionMode", CreationError.CREATION_071));
}
return mode;
}
public String getMesosDispatcherURL(PipelineConfiguration pipelineConf) {
String value = null;
if (pipelineConf.getConfiguration("mesosDispatcherURL") != null) {
value = pipelineConf.getConfiguration("mesosDispatcherURL").getValue().toString();
}
return value;
}
public String getHdfsS3ConfDirectory(PipelineConfiguration pipelineConf) {
String value = null;
if (pipelineConf.getConfiguration("hdfsS3ConfDir") != null) {
value = pipelineConf.getConfiguration("hdfsS3ConfDir").getValue().toString();
}
return value;
}
private StageBean createStageBean(boolean forExecution, StageLibraryTask library, StageConfiguration stageConf,
boolean errorStage, Map<String, Object> constants, List<Issue> errors) {
IssueCreator issueCreator = IssueCreator.getStage(stageConf.getInstanceName());
StageBean bean = null;
StageDefinition stageDef = library.getStage(stageConf.getLibrary(), stageConf.getStageName(),
forExecution);
if (stageDef != null) {
if (stageDef.isErrorStage() != errorStage) {
if (stageDef.isErrorStage()) {
errors.add(issueCreator.create(CreationError.CREATION_007, stageDef.getLibraryLabel(), stageDef.getLabel(),
stageConf.getStageVersion()));
} else {
errors.add(issueCreator.create(CreationError.CREATION_008, stageDef.getLibraryLabel(), stageDef.getLabel(),
stageConf.getStageVersion()));
}
}
bean = createStage(stageDef, library, stageConf, constants, errors);
} else {
errors.add(issueCreator.create(CreationError.CREATION_006, stageConf.getLibrary(), stageConf.getStageName(),
stageConf.getStageVersion()));
}
return bean;
}
@SuppressWarnings("unchecked")
public static StageConfiguration getPipelineConfAsStageConf(PipelineConfiguration pipelineConf) {
return new StageConfiguration(null, "none", "pipeline", pipelineConf.getVersion(), pipelineConf.getConfiguration(),
Collections.EMPTY_MAP, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
}
@SuppressWarnings("unchecked")
private static StageConfiguration getRulesConfAsStageConf(RuleDefinitions ruleDefinitions) {
return new StageConfiguration(
null,
"none",
"pipeline",
ruleDefinitions.getVersion(),
ruleDefinitions.getConfiguration(),
Collections.EMPTY_MAP,
Collections.EMPTY_LIST,
Collections.EMPTY_LIST,
Collections.EMPTY_LIST
);
}
@SuppressWarnings("unchecked")
private PipelineConfigBean createPipelineConfigs(PipelineConfiguration pipelineConf, List<Issue> errors) {
PipelineConfigBean pipelineConfigBean = new PipelineConfigBean();
if (createConfigBeans(pipelineConfigBean, "", PIPELINE_DEFINITION, "pipeline", errors)) {
// To support parameters in Pipeline Configuration inject "parameters" (constants) field first
StageConfiguration stageConf = getPipelineConfAsStageConf(pipelineConf);
Config parametersConfigConf = stageConf.getConfig(PARAMETERS);
ConfigDefinition parametersConfigDef = PIPELINE_DEFINITION.getConfigDefinitionsMap().get(PARAMETERS);
if (parametersConfigConf != null) {
try {
injectConfigValue(
pipelineConfigBean,
PipelineConfigBean.class.getField(PARAMETERS),
PIPELINE_DEFINITION,
stageConf,
parametersConfigDef,
parametersConfigConf,
Collections.EMPTY_MAP,
errors
);
} catch (NoSuchFieldException ex) {
IssueCreator issueCreator = IssueCreator.getStage(stageConf.getStageName());
errors.add(issueCreator.create(CreationError.CREATION_000, parametersConfigDef.getLabel(), ex.toString()));
pipelineConfigBean.constants = Collections.EMPTY_MAP;
}
}
if (pipelineConfigBean.constants == null) {
pipelineConfigBean.constants = Collections.emptyMap();
}
injectConfigs(
pipelineConfigBean,
"",
PIPELINE_DEFINITION.getConfigDefinitionsMap(),
PIPELINE_DEFINITION,
stageConf,
pipelineConfigBean.constants,
errors
);
}
return pipelineConfigBean;
}
// if not null it is OK. if null there was at least one error, check errors for the details
StageBean createStage(StageDefinition stageDef, ClassLoaderReleaser classLoaderReleaser, StageConfiguration stageConf,
Map<String, Object> pipelineConstants, List<Issue> errors) {
Stage stage;
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(stageDef.getStageClassLoader());
stage = createStageInstance(stageDef, stageConf.getInstanceName(), errors);
if (stage != null) {
injectStageConfigs(stage, stageDef, stageConf, pipelineConstants, errors);
}
} finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
StageConfigBean stageConfigBean = createAndInjectStageBeanConfigs(stageDef, stageConf, pipelineConstants, errors);
return (errors.isEmpty()) ? new StageBean(stageDef, stageConf, stageConfigBean, stage, classLoaderReleaser) : null;
}
private Stage createStageInstance(StageDefinition stageDef, String stageName, List<Issue> errors) {
Stage stage = null;
try {
stage = stageDef.getStageClass().newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
IssueCreator issueCreator = IssueCreator.getStage(stageName);
errors.add(issueCreator.create(CreationError.CREATION_000, stageDef.getLabel(), ex.toString()));
}
return stage;
}
private Stage injectStageConfigs(Stage stage, StageDefinition stageDef, StageConfiguration stageConf,
Map<String, Object> pipelineConstants, List<Issue> errors) {
if (createConfigBeans(stage, "", stageDef, stageConf.getInstanceName(), errors)) {
injectConfigs(stage, "", stageDef.getConfigDefinitionsMap(), stageDef, stageConf, pipelineConstants, errors);
}
return stage;
}
private StageConfigBean createAndInjectStageBeanConfigs(StageDefinition stageDef, StageConfiguration stageConf,
Map<String, Object> pipelineConstants, List<Issue> errors) {
StageConfigBean stageConfigBean = new StageConfigBean();
if (createConfigBeans(stageConfigBean, "", stageDef, stageConf.getInstanceName(), errors)) {
//we use the stageDef configdefs because they may hide system configs
injectConfigs(stageConfigBean, "", stageDef.getConfigDefinitionsMap(), stageDef, stageConf, pipelineConstants,
errors);
}
return stageConfigBean;
}
private boolean createConfigBeans(Object obj, String configPrefix, StageDefinition stageDef, String stageName,
List<Issue> errors) {
boolean ok = true;
Class klass = obj.getClass();
for (Field field : klass.getFields()) {
String configName = configPrefix + field.getName();
if (field.getAnnotation(ConfigDefBean.class) != null) {
try {
Object bean = field.getType().newInstance();
if (createConfigBeans(bean, configName + ".", stageDef, stageName, errors)) {
field.set(obj, bean);
}
} catch (InstantiationException | IllegalAccessException ex) {
ok = false;
IssueCreator issueCreator = IssueCreator.getStage(stageName);
errors.add(issueCreator.create(CreationError.CREATION_001, field.getType().getSimpleName(), ex.toString()));
}
}
}
return ok;
}
private void injectConfigs(Object obj, Map<String, Object> valueMap, String configPrefix,
Map<String, ConfigDefinition> configDefMap, StageDefinition stageDef, StageConfiguration stageConf,
Map<String, Object> pipelineConstants, List<Issue> errors) {
String stageName = stageConf.getInstanceName();
IssueCreator issueCreator = IssueCreator.getStage(stageName);
for (Field field : obj.getClass().getFields()) {
String configName = configPrefix + field.getName();
if (field.getAnnotation(ConfigDef.class) != null) {
ConfigDefinition configDef = configDefMap.get(configName);
if (configDef == null) {
errors.add(issueCreator.create(configName, CreationError.CREATION_002, configName));
} else {
Object value = valueMap.get(configName);
if (value == null) {
LOG.warn("Stage '{}' missing configuration '{}', using default", stageName, configDef.getName());
injectDefaultValue(obj, field, stageDef, stageConf, configDef, pipelineConstants, stageName, errors);
} else {
injectConfigValue(obj, field, value, stageDef, stageConf, configDef, null, pipelineConstants, errors);
}
}
} else if (field.getAnnotation(ConfigDefBean.class) != null) {
try {
injectConfigs(field.get(obj), valueMap, configName + ".", configDefMap, stageDef, stageConf,
pipelineConstants, errors);
} catch (IllegalArgumentException | IllegalAccessException ex) {
errors.add(issueCreator.create(CreationError.CREATION_003, ex.toString()));
}
}
}
}
private void injectConfigs(Object obj, String configPrefix, Map<String, ConfigDefinition> configDefMap,
StageDefinition stageDef, StageConfiguration stageConf, Map<String, Object> pipelineConstants,
List<Issue> errors) {
String stageName = stageConf.getInstanceName();
IssueCreator issueCreator = IssueCreator.getStage(stageName);
for (Field field : obj.getClass().getFields()) {
String configName = configPrefix + field.getName();
if (field.getAnnotation(ConfigDef.class) != null) {
ConfigDefinition configDef = configDefMap.get(configName);
// if there is no config def, we ignore it, it can be the case when the config is a @HideConfig
if (configDef != null) {
Config configConf = stageConf.getConfig(configName);
if (configConf == null) {
LOG.warn("Stage '{}' missing configuration '{}', using default", stageName, configDef.getName());
injectDefaultValue(obj, field, stageDef, stageConf, configDef, pipelineConstants, stageName, errors);
} else {
injectConfigValue(obj, field, stageDef, stageConf, configDef, configConf, pipelineConstants, errors);
}
}
} else if (field.getAnnotation(ConfigDefBean.class) != null) {
try {
injectConfigs(field.get(obj), configName + ".", configDefMap, stageDef, stageConf, pipelineConstants, errors);
} catch (IllegalArgumentException | IllegalAccessException ex) {
errors.add(issueCreator.create(CreationError.CREATION_003, ex.toString()));
}
}
}
}
private void injectDefaultValue(Object obj, Field field, StageDefinition stageDef, StageConfiguration stageConf,
ConfigDefinition configDef, Map<String, Object> pipelineConstants, String stageName, List<Issue> errors) {
Object defaultValue = configDef.getDefaultValue();
if (defaultValue != null) {
injectConfigValue(obj, field, defaultValue, stageDef, stageConf, configDef, null, pipelineConstants, errors);
} else if (!hasJavaDefault(obj, field)) {
defaultValue = configDef.getType().getDefault(field.getType());
injectConfigValue(obj, field, defaultValue, stageDef, stageConf, configDef, null, pipelineConstants, errors);
}
}
private boolean hasJavaDefault(Object obj, Field field) {
try {
return field.get(obj) != null;
} catch (Exception ex) {
throw new RuntimeException(Utils.format("Unexpected exception: {}", ex.toString()), ex);
}
}
@SuppressWarnings("unchecked")
Object toEnum(Class klass, Object value, StageDefinition stageDef, String stageName, String groupName,
String configName, List<Issue> errors) {
try {
value = Enum.valueOf(klass, value.toString());
} catch (IllegalArgumentException ex) {
IssueCreator issueCreator = IssueCreator.getStage(stageName);
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_010, value, klass.getSimpleName(),
ex.toString()));
value = null;
}
return value;
}
Object toString(Object value, StageDefinition stageDef, String stageName, String groupName, String configName,
List<Issue> errors) {
if (!(value instanceof String)) {
IssueCreator issueCreator = IssueCreator.getStage(stageName);
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_011, value,
value.getClass().getSimpleName()));
value = null;
}
return value;
}
Object toChar(Object value, StageDefinition stageDef, String stageName, String groupName, String configName,
List<Issue> errors) {
IssueCreator issueCreator = IssueCreator.getStage(stageName);
if (value instanceof String) {
String strValue = value.toString();
if (!strValue.isEmpty() && strValue.startsWith("\\u") && strValue.length() > 5 &&
strValue.substring(2).matches("^[0-9a-fA-F]+$")) {
// To support non printable unicode control characters
value = (char) Integer.parseInt(strValue.substring(2), 16 );
} else if (strValue.isEmpty() || strValue.length() > 1) {
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_012, value, strValue));
value = null;
} else {
value = strValue.charAt(0);
}
} else if (!(value instanceof Character)) {
String valueType = value == null ? "null" : value.getClass().getName();
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_012, value, valueType));
value = null;
}
return value;
}
Object toBoolean(Object value, StageDefinition stageDef, String stageName, String groupName, String configName,
List<Issue> errors) {
if (!(value instanceof Boolean)) {
IssueCreator issueCreator = IssueCreator.getStage(stageName);
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_013, value));
value = null;
}
return value;
}
private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPER_MAP
= new ImmutableMap.Builder<Class<?>, Class<?>>()
.put(byte.class, Byte.class)
.put(short.class, Short.class)
.put(int.class, Integer.class)
.put(long.class, Long.class)
.put(float.class, Float.class)
.put(double.class, Double.class)
.build();
private static final Map<Class<?>, Method> WRAPPERS_VALUE_OF_MAP = new HashMap<>();
@SuppressWarnings("unchecked")
private static Method getValueOfMethod(Class klass) {
try {
return klass.getMethod("valueOf", String.class);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
static {
for (Class klass : PRIMITIVE_WRAPPER_MAP.values()) {
WRAPPERS_VALUE_OF_MAP.put(klass, getValueOfMethod(klass));
}
}
Object toNumber(Class numberType, Object value, StageDefinition stageDef, String stageName, String groupName,
String configName, List<Issue> errors) {
IssueCreator issueCreator = IssueCreator.getStage(stageName);
if (!ConfigValueExtractor.NUMBER_TYPES.contains(value.getClass())) {
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_014, value));
value = null;
} else {
try {
if (PRIMITIVE_WRAPPER_MAP.containsKey(numberType)) {
numberType = PRIMITIVE_WRAPPER_MAP.get(numberType);
}
value = WRAPPERS_VALUE_OF_MAP.get(numberType).invoke(null, value.toString());
} catch (Exception ex) {
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_015, value,
numberType.getSimpleName(), ex.toString()));
value = null;
}
}
return value;
}
Object toList(Object value, StageDefinition stageDef, ConfigDefinition configDef,
Map<String, Object> pipelineConstants, String stageName, String groupName, String configName,
List<Issue> errors, Field field) {
IssueCreator issueCreator = IssueCreator.getStage(stageName);
if (!(value instanceof List)) {
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_020));
value = null;
} else {
boolean error = false;
List<Object> list = new ArrayList<>();
for (Object element : (List) value) {
if (element == null) {
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_021));
error = true;
} else {
element = resolveIfImplicitEL(element, stageDef, configDef, pipelineConstants, stageName, errors);
if (element != null) {
//We support list of String and enums.
//If the field type is enum and the element is String, convert to enum
if(field != null) {
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
Type type1 = ((ParameterizedType) type).getActualTypeArguments()[0];
if(type1 instanceof Class && ((Class<?>)type1).isEnum()) {
element = toEnum((Class<?>)type1, element, stageDef, stageName, groupName, configName, errors);
}
}
}
list.add(element);
} else {
error = true;
}
}
}
value = (error) ? null : list;
}
return value;
}
@SuppressWarnings("unchecked")
Object toMap(Object value, StageDefinition stageDef, ConfigDefinition configDef,
Map<String, Object> pipelineConstants, String stageName, String groupName, String configName,
List<Issue> errors) {
IssueCreator issueCreator = IssueCreator.getStage(stageName);
if (!(value instanceof List)) {
// This should be a list of maps because in JSON we represent it as
// [{"key": "actual key name", "value": "your value"}]
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_030));
value = null;
} else {
boolean error = false;
Map map = new LinkedHashMap();
for (Object entry : (List) value) {
if (!(entry instanceof Map)) {
error = true;
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_031,
entry.getClass().getSimpleName()));
} else {
Object k = ((Map)entry).get("key");
if (k == null) {
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_032));
}
Object v = ((Map)entry).get("value");
if (v == null) {
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_033));
} else {
v = resolveIfImplicitEL(v, stageDef, configDef, pipelineConstants, stageName, errors);
}
if (k != null && v != null) {
map.put(k, v);
} else {
error = true;
}
}
}
value = (error) ? null : map;
}
return value;
}
@SuppressWarnings("unchecked")
private Object toComplexField(Object value, StageDefinition stageDef, StageConfiguration stageConf,
ConfigDefinition configDef, Config configConf, Map<String, Object> pipelineConstants,
List<Issue> errors) {
String stageName = stageConf.getInstanceName();
IssueCreator issueCreator = IssueCreator.getStage(stageName);
if (!(value instanceof List)) {
errors.add(issueCreator.create(configDef.getGroup(), configDef.getName(), CreationError.CREATION_040,
value.getClass().getSimpleName()));
value = null;
} else {
boolean error = false;
List<Object> list = new ArrayList<>();
String className = configDef.getModel().getListBeanClass().getName();
try {
// we need to use the classloader fo the stage to instatiate the ComplexField so if the stage has a private
// classloader we use the same one.
Class klass = Thread.currentThread().getContextClassLoader().loadClass(className);
List listValue = (List) value;
for (int i = 0; i < listValue.size(); i++) {
Map<String, Object> configElement;
try {
configElement = (Map<String, Object>) listValue.get(i);
try {
Object element = klass.newInstance();
if (createConfigBeans(element, configDef.getName() + ".", stageDef, stageConf.getInstanceName(), errors)) {
injectConfigs(element, configElement, "", configDef.getModel().getConfigDefinitionsAsMap(), stageDef,
stageConf, pipelineConstants, errors);
list.add(element);
}
} catch (InstantiationException | IllegalAccessException ex) {
errors.add(issueCreator.create(configDef.getGroup(), Utils.format("{}[{}]", configConf.getName(), i),
CreationError.CREATION_041, klass.getSimpleName(), ex.toString()));
error = true;
break;
}
} catch (ClassCastException ex) {
errors.add(issueCreator.create(configDef.getGroup(), Utils.format("{}[{}]", configConf.getName(), i),
CreationError.CREATION_042, ex.toString()));
}
}
value = (error) ? null : list;
} catch (ClassNotFoundException ex) {
value = null;
LOG.debug("Can't load class {}", className, ex);
errors.add(issueCreator.create(
configDef.getGroup(),
configConf.getName(),
CreationError.CREATION_043,
ex.toString(),
Thread.currentThread().getContextClassLoader().toString()
));
}
}
return value;
}
private Object resolveIfImplicitEL(Object value, StageDefinition stageDef, ConfigDefinition configDef,
Map<String, Object> pipelineConstants, String stageName, List<Issue> errors) {
IssueCreator issueCreator = IssueCreator.getStage(stageName);
if (configDef.getEvaluation() == ConfigDef.Evaluation.IMPLICIT && value instanceof String &&
ElUtil.isElString(value)) {
try {
value = ElUtil.evaluate(value, stageDef, configDef, pipelineConstants);
} catch (ELEvalException ex) {
errors.add(issueCreator.create(configDef.getGroup(), configDef.getName(), CreationError.CREATION_005,
value, ex.toString()));
value = null;
}
}
return value;
}
private void injectConfigValue(Object obj, Field field, StageDefinition stageDef, StageConfiguration stageConf,
ConfigDefinition configDef, Config configConf, Map<String, Object> pipelineConstants,
List<Issue> errors) {
Object value = configConf.getValue();
if (value == null) {
injectDefaultValue(obj, field, stageDef, stageConf, configDef, pipelineConstants, stageConf.getInstanceName(),
errors);
} else {
injectConfigValue(obj, field, value, stageDef, stageConf, configDef, configConf, pipelineConstants, errors);
}
}
private void injectConfigValue(Object obj, Field field, Object value, StageDefinition stageDef, StageConfiguration stageConf,
ConfigDefinition configDef, Config configConf, Map<String, Object> pipelineConstants,
List<Issue> errors) {
String stageName = stageConf.getInstanceName();
IssueCreator issueCreator = IssueCreator.getStage(stageName);
String groupName = configDef.getGroup();
String configName = configDef.getName();
if (value == null) {
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_050));
} else {
if (configDef.getModel() != null && configDef.getModel().getModelType() == ModelType.LIST_BEAN) {
value = toComplexField(value, stageDef, stageConf, configDef, configConf, pipelineConstants, errors);
} else if (List.class.isAssignableFrom(field.getType())) {
value = toList(value, stageDef, configDef, pipelineConstants, stageName, groupName, configName, errors, field);
} else if (Map.class.isAssignableFrom(field.getType())) {
value = toMap(value, stageDef, configDef, pipelineConstants, stageName, groupName, configName, errors);
} else {
value = resolveIfImplicitEL(value, stageDef, configDef, pipelineConstants, stageName, errors);
if (value != null) {
if (field.getType().isEnum()) {
value = toEnum(field.getType(), value, stageDef, stageName, groupName, configName, errors);
} else if (field.getType() == String.class) {
value = toString(value, stageDef, stageName, groupName, configName, errors);
} else if (List.class.isAssignableFrom(field.getType())) {
value = toList(value, stageDef, configDef, pipelineConstants, stageName, groupName, configName, errors,
field);
} else if (Map.class.isAssignableFrom(field.getType())) {
value = toMap(value, stageDef, configDef, pipelineConstants, stageName, groupName, configName, errors);
} else if (ConfigValueExtractor.CHARACTER_TYPES.contains(field.getType())) {
value = toChar(value, stageDef, stageName, groupName, configName, errors);
} else if (ConfigValueExtractor.BOOLEAN_TYPES.contains(field.getType())) {
value = toBoolean(value, stageDef, stageName, groupName, configName, errors);
} else if (ConfigValueExtractor.NUMBER_TYPES.contains(field.getType())) {
value = toNumber(field.getType(), value, stageDef, stageName, groupName, configName, errors);
} else {
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_051,
field.getType().getSimpleName()));
value = null;
}
}
}
if (value != null) {
try {
field.set(obj, value);
} catch (IllegalAccessException ex) {
errors.add(issueCreator.create(groupName, configName, CreationError.CREATION_060, value, ex.toString()));
}
}
}
}
}