package org.ovirt.engine.core.utils;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.common.businessentities.VdcOption;
import org.ovirt.engine.core.common.config.ConfigCommon;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.config.DefaultValueAttribute;
import org.ovirt.engine.core.common.config.IConfigUtilsInterface;
import org.ovirt.engine.core.common.config.OptionBehaviourAttribute;
import org.ovirt.engine.core.common.config.TypeConverterAttribute;
import org.ovirt.engine.core.compat.Version;
import org.ovirt.engine.core.utils.crypt.EngineEncryptionUtils;
import org.ovirt.engine.core.utils.serialization.json.JsonObjectDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Config Utils Base Class
*/
public abstract class ConfigUtilsBase implements IConfigUtilsInterface {
private static final Logger log = LoggerFactory.getLogger(ConfigUtilsBase.class);
@Override
public abstract <T> T getValue(ConfigValues configValue, String version);
public static final class EnumValue {
final Class<?> fieldType;
final String defaultValue;
final OptionBehaviourAttribute optionBehaviour;
public EnumValue(Class<?> fieldType, String defaultValue, OptionBehaviourAttribute optionBehaviour) {
super();
this.fieldType = fieldType;
this.defaultValue = defaultValue;
this.optionBehaviour = optionBehaviour;
}
public Class<?> getFieldType() {
return fieldType;
}
public String getDefaultValue() {
return defaultValue;
}
public OptionBehaviourAttribute getOptionBehaviour() {
return optionBehaviour;
}
}
private static final String TEMP = "Temp";
/**
* Returns the typed value of the given option. returns default value if option.option_value is null
*/
protected Object getValue(VdcOption option) {
Object result = option.getOptionValue();
EnumValue enumValue = parseEnumValue(option.getOptionName());
if (enumValue != null) {
final OptionBehaviourAttribute optionBehaviour = enumValue.getOptionBehaviour();
final Class<?> fieldType = enumValue.getFieldType();
final String defaultValue = enumValue.getDefaultValue();
result = parseValue(option.getOptionValue(), option.getOptionName(), fieldType);
// if null use default from @DefaultValueAttribute
if (result == null) {
result = parseValue(defaultValue, option.getOptionName(), fieldType);
}
if (optionBehaviour != null) {
Map<String, Object> values = null;
switch (optionBehaviour.behaviour()) {
// split string by comma for List<string> constructor
case CommaSeparatedStringArray:
result = Arrays.asList(((String) result).split("[,]", -1));
break;
case Password:
try {
result = EngineEncryptionUtils.decrypt((String) result);
} catch (Exception e) {
log.error("Failed to decrypt value for property '{}', encrypted value will be used. Error: {} ",
option.getOptionName(), e.getMessage());
log.debug("Exception", e);
}
break;
case ValueDependent:
// get the config that this value depends on
String prefix = getValue(optionBehaviour.dependentOn(), ConfigCommon.defaultConfigurationVersion);
// combine the prefix with the 'real value'
if (prefix != null) {
String realName = String.format("%1$s%2$s", prefix, optionBehaviour.realValue());
result = getValue(ConfigValues.valueOf(realName), ConfigCommon.defaultConfigurationVersion);
}
break;
case CommaSeparatedVersionArray:
HashSet<Version> versions = new HashSet<>();
for (String ver : result.toString().split("[,]", -1)) {
try {
versions.add(new Version(ver));
} catch (Exception e) {
log.error("Could not parse version '{}' for config value '{}'",
ver, option.getOptionName());
}
}
result = versions;
break;
}
}
}
return result;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static Object parseValue(String value, String name, Class<?> fieldType) {
if (value == null) {
return null;
}
try {
if (fieldType == Integer.class) {
return Integer.parseInt(value);
} else if (fieldType == Long.class) {
return Long.parseLong(value);
} else if (fieldType == Boolean.class) {
return Boolean.parseBoolean(value);
} else if (fieldType == Version.class) {
return new Version(value);
} else if (fieldType == Date.class) {
return new SimpleDateFormat("k:m:s").parse(value);
} else if (fieldType == Double.class) {
return Double.parseDouble(value);
} else if (Map.class.isAssignableFrom(fieldType)) {
/* This if statement is to make a difference between the old and the json-style
* map support:
* - if it is enclosed between brackets, then it is handled as json object and a map is returned
* - otherwise it is handled as a string, the client code handles parsing it
*/
if(StringUtils.startsWith(value, "{") && StringUtils.endsWith(value, "}")) {
return new JsonObjectDeserializer().deserialize(value, HashMap.class);
} else {
return value;
}
} else if (fieldType.isEnum()) {
return Enum.valueOf((Class<Enum>)fieldType, value.toUpperCase());
} else {
return value;
}
} catch (Exception e2) {
log.error("Error parsing option '{}' value: {}", name, e2.getMessage());
log.debug("Exception", e2);
return null;
}
}
/**
* parse the enum value by its attributes and return the type, default value, and option behaviour (if any) return
* false if cannot find value in enum or cannot get type
*/
protected static EnumValue parseEnumValue(String name) {
// get field from enum for its attributes
Field fi = null;
try {
fi = ConfigValues.class.getField(name);
} catch (Exception ex) {
// eat this exception. it is not fatal and will be handled like
// fi==null;
}
if (fi == null) {
// Ignore temporary values inserted to vdc_options by upgrades as
// flags.
if (!name.startsWith(TEMP)) {
log.warn("Could not find enum value for option: '{}'", name);
}
return null;
} else {
// get type
if (fi.isAnnotationPresent(TypeConverterAttribute.class)) {
final Class<?> fieldType = fi.getAnnotation(TypeConverterAttribute.class).value();
String defaultValue = null;
OptionBehaviourAttribute optionBehaviour = null;
// get default value
if (fi.isAnnotationPresent(DefaultValueAttribute.class)) {
defaultValue = fi.getAnnotation(DefaultValueAttribute.class).value();
}
// get the attribute for default behaviour
if (fi.isAnnotationPresent(OptionBehaviourAttribute.class)) {
optionBehaviour = fi.getAnnotation(OptionBehaviourAttribute.class);
}
return new EnumValue(fieldType, defaultValue, optionBehaviour);
} else {
// if could not get type then cannot continue
return null;
}
}
}
}