/** * 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.definition; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.Primitives; import com.streamsets.datacollector.json.ObjectMapperFactory; import com.streamsets.datacollector.util.ElUtil; import com.streamsets.pipeline.api.ConfigDef; import com.streamsets.pipeline.api.impl.ErrorMessage; import com.streamsets.pipeline.api.impl.Utils; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; public abstract class ConfigValueExtractor { private static final ConfigValueExtractor EXTRACTOR = new ConfigValueExtractor() {}; public static ConfigValueExtractor get() { return EXTRACTOR; } public final static Set<Class> BOOLEAN_TYPES = ImmutableSet.<Class>of(Boolean.class, Boolean.TYPE); public final static Set<Class> NUMBER_TYPES = ImmutableSet.<Class>of(Byte.class, Byte.TYPE, Short.class, Short.TYPE, Integer.class, Integer.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE, Double.class, Double.TYPE); public final static Set<Class> CHARACTER_TYPES = ImmutableSet.<Class>of(Character.class, Character.TYPE); @SuppressWarnings("unchecked") public List<ErrorMessage> validate(Field field, ConfigDef.Type type, String valueStr, Object contextMsg, boolean isTrigger) { List<ErrorMessage> errors = new ArrayList<>(); if (!valueStr.isEmpty()) { if (ElUtil.isElString(valueStr)) { if (isTrigger) { errors.add(new ErrorMessage(DefinitionError.DEF_000, contextMsg, valueStr)); } } else { switch (type) { case BOOLEAN: if (!BOOLEAN_TYPES.contains(field.getType())) { errors.add(new ErrorMessage(DefinitionError.DEF_001, contextMsg, field.getType())); } break; case NUMBER: if (!NUMBER_TYPES.contains(field.getType())) { errors.add(new ErrorMessage(DefinitionError.DEF_002, contextMsg)); } try { Class<?> wrapper; if (Primitives.isWrapperType(field.getType())) { wrapper = field.getType(); } else { wrapper = Primitives.wrap(field.getType()); } wrapper.getMethod("valueOf", String.class).invoke(null, valueStr); } catch (Exception ex) { errors.add(new ErrorMessage(DefinitionError.DEF_013, contextMsg, valueStr)); } break; case STRING: case MODEL: if (!String.class.isAssignableFrom(field.getType()) && !field.getType().isEnum() && !List.class.isAssignableFrom(field.getType())) { errors.add(new ErrorMessage(DefinitionError.DEF_003, contextMsg, field.getType())); } if (field.getType().isEnum()) { try { Enum.valueOf(((Class<Enum>) field.getType()), valueStr); } catch (IllegalArgumentException ex) { errors.add(new ErrorMessage(DefinitionError.DEF_004, contextMsg, field.getType(), valueStr)); } } else if(List.class.isAssignableFrom(field.getType())) { try { List list = ObjectMapperFactory.get().readValue(valueStr, List.class); try { // convert to enum if necessary to validate convertElementsToEnum(field, list); } catch (Exception ex) { errors.add(new ErrorMessage(DefinitionError.DEF_012, contextMsg, getListType(field).getSimpleName(), ex.toString())); } } catch (Exception ex) { errors.add(new ErrorMessage(DefinitionError.DEF_006, contextMsg, valueStr, ex.toString())); } } break; case LIST: if (!List.class.isAssignableFrom(field.getType())) { errors.add(new ErrorMessage(DefinitionError.DEF_005, contextMsg, field.getType())); } try { List list = ObjectMapperFactory.get().readValue(valueStr, List.class); try { // convert to enum if necessary to validate convertElementsToEnum(field, list); } catch (Exception ex) { errors.add(new ErrorMessage(DefinitionError.DEF_012, contextMsg, getListType(field).getSimpleName(), ex.toString())); } } catch (Exception ex) { errors.add(new ErrorMessage(DefinitionError.DEF_006, contextMsg, valueStr, ex.toString())); } break; case MAP: if (!Map.class.isAssignableFrom(field.getType())) { errors.add(new ErrorMessage(DefinitionError.DEF_007, contextMsg, field.getType())); } try { ObjectMapperFactory.get().readValue(valueStr, LinkedHashMap.class); } catch (Exception ex) { errors.add(new ErrorMessage(DefinitionError.DEF_008, contextMsg, valueStr)); } break; case CHARACTER: if (!CHARACTER_TYPES.contains(field.getType())) { errors.add(new ErrorMessage(DefinitionError.DEF_009, contextMsg)); } if (valueStr.length() > 1) { errors.add(new ErrorMessage(DefinitionError.DEF_010, contextMsg, valueStr)); } break; case TEXT: if (!String.class.isAssignableFrom(field.getType())) { errors.add(new ErrorMessage(DefinitionError.DEF_011, contextMsg, field.getType())); } break; } } } return errors; } public List<ErrorMessage> validate(Field field, ConfigDef def, Object contextMsg) { return validate(field, def.type(), def.defaultValue(), contextMsg, false); } public Object extract(Field field, ConfigDef def, Object contextMsg) { return extract(field, def.type(), def.defaultValue(), contextMsg, false); } @SuppressWarnings("unchecked") public Object extract(Field field, ConfigDef.Type type, String valueStr, Object contextMsg, boolean isTrigger) { List<ErrorMessage> errors = validate(field, type, valueStr, contextMsg, isTrigger); if (errors.isEmpty()) { Object value = null; if (valueStr == null || valueStr.isEmpty()) { value = null; } else { if (ElUtil.isElString(valueStr)) { value = valueStr; } else { try { switch (type) { case BOOLEAN: value = Boolean.parseBoolean(valueStr); break; case NUMBER: if (field.getType() == Byte.TYPE || field.getType() == Byte.class) { value = Byte.parseByte(valueStr); } else if (field.getType() == Short.TYPE || field.getType() == Short.class) { value = Short.parseShort(valueStr); } else if (field.getType() == Integer.TYPE || field.getType() == Integer.class) { value = Integer.parseInt(valueStr); } else if (field.getType() == Long.TYPE || field.getType() == Long.class) { value = Long.parseLong(valueStr); } else if (field.getType() == Float.TYPE || field.getType() == Float.class) { value = Float.parseFloat(valueStr); } else if (field.getType() == Double.TYPE || field.getType() == Double.class) { value = Double.parseDouble(valueStr); } break; case STRING: case MODEL: if(field.getType() == String.class) { value = valueStr; } else if(field.getType().isEnum()){ value = Enum.valueOf(((Class<Enum>)field.getType()), valueStr); } else if(List.class.isAssignableFrom(field.getType())) { value = ObjectMapperFactory.get().readValue(valueStr, List.class); // convert to enum if necessary value = convertElementsToEnum(field, (List) value); } break; case LIST: value = ObjectMapperFactory.get().readValue(valueStr, List.class); // convert to enum if necessary value = convertElementsToEnum(field, (List) value); break; case MAP: Map<String, ?> map = ObjectMapperFactory.get().readValue(valueStr, LinkedHashMap.class); List list = new ArrayList(); for (Map.Entry<String, ?> entry : map.entrySet()) { list.add(ImmutableMap.of("key", entry.getKey(), "value", entry.getValue())); } value = list; break; case CHARACTER: value = valueStr.charAt(0); break; case TEXT: value = valueStr; break; } } catch (IOException ex) { throw new RuntimeException(Utils.format("Unexpected exception: {}", ex.toString()), ex); } } } return value; } else { throw new IllegalArgumentException(Utils.format("Invalid configuration value: {}", errors)); } } Class getListType(Field listField) { return (Class)((ParameterizedType)listField.getGenericType()).getActualTypeArguments()[0]; } @SuppressWarnings("unchecked") List convertElementsToEnum(Field listField, List list) { Class elementClass = getListType(listField); if (elementClass.isEnum()) { for (int i = 0; i < list.size(); i++) { list.set(i, Enum.valueOf(elementClass, (String) list.get(i))); } } return list; } }