package org.ovirt.engine.core.utils.customprop;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.common.businessentities.VmDeviceGeneralType;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.utils.customprop.CustomPropertiesUtils;
import org.ovirt.engine.core.common.utils.customprop.ValidationError;
import org.ovirt.engine.core.common.utils.customprop.ValidationFailureReason;
import org.ovirt.engine.core.common.utils.exceptions.InitializationException;
import org.ovirt.engine.core.compat.Version;
/**
* A class providing helper methods to work with device custom properties
*/
public class DevicePropertiesUtils extends CustomPropertiesUtils {
/**
* Singleton instance of the class
*/
private static final DevicePropertiesUtils devicePropertiesUtils;
static {
devicePropertiesUtils = new DevicePropertiesUtils();
}
/**
* Prefix for device type definition
*/
private static final String TYPE_PREFIX_STR = "\\{type=(";
/**
* Length of prefix for device type definition
*/
private static final int TYPE_PREFIX_LEN = 6;
/**
* Prefix for device properties specification
*/
private static final String PROP_PREFIX_STR = ");prop=\\{";
/**
* Length of prefix for device properties specification
*/
private static final int PROP_PREFIX_LEN = 7;
/**
* Suffix of device type properties specification
*/
private static final String PROP_SUFFIX_STR = "\\}\\}[;]?";
/**
* Device type delimiter for validation parsing pattern
*/
private static final String DEV_TYPE_DELIM = "|";
/**
* String to create pattern to parse device custom properties specification
*/
private final String devicePropSplitStr;
/**
* Pattern to parse device custom properties specification
*/
private final Pattern devicePropSplitPattern;
/**
* String to create pattern to validate device custom properties specification
*/
private final String devicePropValidationStr;
/**
* Pattern to validate device custom properties specification
*/
private final Pattern devicePropValidationPattern;
/**
* Map of device custom properties for each version and device type
*/
private Map<Version, EnumMap<VmDeviceGeneralType, Map<String, String>>> deviceProperties;
/**
* List of device types for which custom properties can be set
*/
private final List<VmDeviceGeneralType> supportedDeviceTypes;
/**
* Error thrown if device custom properties are to be set for a device with UNKNOWN type
*/
protected final List<ValidationError> invalidDeviceTypeValidationError;
/**
* Creates an instance and initializes device custom properties patterns. Constructor is package visible for testing
* purposes.
*/
DevicePropertiesUtils() {
// init supported device types
supportedDeviceTypes = new ArrayList<>();
for (VmDeviceGeneralType t : VmDeviceGeneralType.values()) {
// custom properties cannot be set for UNKNOWN
if (t != VmDeviceGeneralType.UNKNOWN) {
supportedDeviceTypes.add(t);
}
}
StringBuilder sb = new StringBuilder();
// init pattern to parse properties
sb.append(TYPE_PREFIX_STR);
for (VmDeviceGeneralType t : supportedDeviceTypes) {
sb.append(t.getValue());
sb.append(DEV_TYPE_DELIM);
}
sb.deleteCharAt(sb.length() - 1);
sb.append(PROP_PREFIX_STR);
sb.append(VALIDATION_STR);
sb.append(PROP_SUFFIX_STR);
devicePropSplitStr = sb.toString();
devicePropSplitPattern = Pattern.compile(devicePropSplitStr);
// init pattern to validate device properties definition
sb = new StringBuilder();
for (VmDeviceGeneralType t : supportedDeviceTypes) {
sb.append("((");
sb.append(TYPE_PREFIX_STR);
sb.append(t.getValue());
sb.append(PROP_PREFIX_STR);
sb.append(VALIDATION_STR);
sb.append(PROP_SUFFIX_STR);
sb.append(")?)");
}
devicePropValidationStr = sb.toString();
devicePropValidationPattern = Pattern.compile(devicePropValidationStr);
invalidDeviceTypeValidationError =
Arrays.asList(new ValidationError(ValidationFailureReason.INVALID_DEVICE_TYPE, ""));
}
/**
* Returns device custom properties definition for specified version. It exists for testing purposes only.
*
* @param version
* specified version
* @return device custom properties definition for specified version
*/
String getCustomDeviceProperties(Version version) {
return Config.<String> getValue(ConfigValues.CustomDeviceProperties, version.getValue());
}
/**
* Returns instance of the class
*
* @return instance of the class
*/
public static DevicePropertiesUtils getInstance() {
return devicePropertiesUtils;
}
/**
* Loads device custom properties definition
*
* @throws InitializationException
* if an error occured during device custom properties definition loading
*/
public void init() throws InitializationException {
try {
Set<Version> versions = getSupportedClusterLevels();
String devicePropertiesStr;
deviceProperties = new HashMap<>();
for (Version version : versions) {
// load device properties
devicePropertiesStr = getCustomDeviceProperties(version);
deviceProperties.put(version, new EnumMap<>(VmDeviceGeneralType.class));
Matcher typeMatcher = devicePropSplitPattern.matcher(devicePropertiesStr);
while (typeMatcher.find()) {
String dcpStr = typeMatcher.group();
// device type definition starts with "{type="
int start = TYPE_PREFIX_LEN;
int end = dcpStr.length() - 1;
if (dcpStr.endsWith(PROPERTIES_DELIMETER)) {
// remove trailing ;
end--;
}
dcpStr = dcpStr.substring(start, end);
int idx = dcpStr.indexOf(PROPERTIES_DELIMETER);
VmDeviceGeneralType type = VmDeviceGeneralType.forValue(dcpStr.substring(0, idx));
// properties definition for device starts with ";prop={"
String propStr = dcpStr.substring(idx + PROP_PREFIX_LEN, dcpStr.length() - 1);
Map<String, String> props = new HashMap<>();
parsePropertiesRegex(propStr, props);
deviceProperties.get(version).put(type, props);
}
}
} catch (Exception ex) {
throw new InitializationException(ex);
}
}
/**
* Returns set of device types which have defined properties in specified version
*
* @param version
* version of the cluster that the VM containing the device is in
*/
public Set<VmDeviceGeneralType> getDeviceTypesWithProperties(Version version) {
EnumMap<VmDeviceGeneralType, Map<String, String>> map = deviceProperties.get(version);
if (map.isEmpty()) {
// no device type has any properties
return Collections.emptySet();
} else {
// prevent client to modify
return Collections.unmodifiableSet(map.keySet());
}
}
/**
* Returns map of device properties [property name, pattern to validate property value] for specified version and
* device type
*
* @param version
* version of the cluster that the VM containing the device is in
* @param type
* specified device type
* @return map of device properties
*/
public Map<String, String> getDeviceProperties(Version version, VmDeviceGeneralType type) {
Map<String, String> map = deviceProperties.get(version).get(type);
if (map == null) {
// no defined properties for specified type
map = new HashMap<>();
} else {
// prevent client to modify map of properties
map = new HashMap<>(map);
}
return map;
}
/**
* Validates the custom properties of a device. These errors are checked during validation:
* <ol>
* <li>Device custom properties are supported in version</li>
* <li>Device custom properties can be assigned to specified type</li>
* <li>Device custom properties syntax is ok</li>
* <li>Device custom property is defined for specified version and type</li>
* <li>Device custom property value matches its value constrains</li>
* </ol>
*
* @param version
* version of the cluster that the VM containing the device is in
* @param type
* type of a device
* @param properties
* device custom properties
* @return list of errors appeared during validation
*/
public List<ValidationError> validateProperties(Version version, VmDeviceGeneralType type,
Map<String, String> properties) {
if (properties == null || properties.isEmpty()) {
// No errors in case of empty value
return Collections.emptyList();
}
if (!supportedDeviceTypes.contains(type)) {
return invalidDeviceTypeValidationError;
}
if (syntaxErrorInProperties(properties)) {
return invalidSyntaxValidationError;
}
List<ValidationError> results = new ArrayList<>();
Set<ValidationError> errorsSet = new HashSet<>();
for (Map.Entry<String, String> e : properties.entrySet()) {
String key = e.getKey();
if (key == null || !deviceProperties.get(version).get(type).containsKey(key)) {
errorsSet.add(new ValidationError(ValidationFailureReason.KEY_DOES_NOT_EXIST, key));
continue;
}
String value = StringUtils.defaultString(e.getValue());
if (!value.matches(deviceProperties.get(version).get(type).get(key))) {
errorsSet.add(new ValidationError(ValidationFailureReason.INCORRECT_VALUE, key));
continue;
}
}
results.addAll(errorsSet);
return results;
}
/**
* Validates device custom properties definition
*
* @param propDef
* device custom properties definition
* @return {@code true} if device custom properties definition is valid, otherwise {@code false}
*/
public boolean isDevicePropertiesDefinitionValid(String propDef) {
return devicePropValidationPattern.matcher(propDef).matches();
}
/**
* Returns the string describing format of custom device properties specification
*
* @return the string describing format of custom device properties specification
*/
public String getDevicePropertiesDefinition() {
return devicePropSplitStr;
}
}