/* * RHQ Management Platform * Copyright (C) 2005-2013 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.enterprise.server.rest.helper; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.Property; import org.rhq.core.domain.configuration.PropertyList; import org.rhq.core.domain.configuration.PropertyMap; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.configuration.definition.ConfigurationDefinition; import org.rhq.core.domain.configuration.definition.PropertyDefinition; import org.rhq.core.domain.configuration.definition.PropertyDefinitionList; import org.rhq.core.domain.configuration.definition.PropertyDefinitionMap; import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple; import org.rhq.core.domain.configuration.definition.PropertySimpleType; /** * Helper class to deal with configuration objects * @author Heiko W. Rupp */ public class ConfigurationHelper { /** * Convert the passed map into a RHQ configuration object * @param in Map with items to convert. Map.Entry.Key is the name of the property and Map.Entry.Value the value * @return a new Configuration object */ public static Configuration mapToConfiguration(Map<String, Object> in) { Configuration config = new Configuration(); Set<String> mapKeys = in.keySet(); for (String mapKey : mapKeys) { Object mapValue = in.get(mapKey); if (mapValue instanceof Map) { Map<String,Object> map = (Map<String, Object>) mapValue; PropertyMap propertyMap = getPropertyMap(mapKey, map); config.put(propertyMap); } else if (mapValue instanceof List) { List<Object> objects = (List<Object>) mapValue; PropertyList propertyList = getPropertyList(mapKey, objects); config.put(propertyList); } else { config.put(new PropertySimple(mapKey,mapValue)); } } return config; } /** * convert passed configuration to generic map * @param configuration to be converted * @param definition to convert to proper types, can be null, but strict has to be set to false. If null, all simple properties will be rendered as strings * @param strict if enabled all configuration properties must have appropriate property definitions * @return generic map * @throws IllegalArgumentException if strict is true and configuration contains property without matching property definition */ public static Map<String,Object> configurationToMap(Configuration configuration, ConfigurationDefinition definition, boolean strict) { Map<String,Object> result = new HashMap<String, Object>(); if (configuration==null) { return result; } if (configuration.getProperties().isEmpty()) { return result; } for (Property property : configuration.getProperties()) { String propertyName = property.getName(); PropertyDefinition propertyDefinition = null; if (definition != null) { propertyDefinition = definition.get(propertyName); } if (propertyDefinition==null) { if (strict) { throw new IllegalArgumentException("No definition for property " + propertyName + "found"); } } Object target = convertProperty(property, propertyDefinition, strict); result.put(propertyName,target); } return result; } private static Object convertProperty(Property property, PropertyDefinition propertyDefinition, boolean strict) { Object target; if (property instanceof PropertyMap) { PropertyMap propertyMap = (PropertyMap) property; target = getInnerMap(propertyMap,(PropertyDefinitionMap) propertyDefinition, strict); } else if (property instanceof PropertyList) { PropertyList propertyList = (PropertyList) property; target = getInnerList(propertyList, (PropertyDefinitionList)propertyDefinition, strict); } else { target= convertSimplePropertyValue((PropertySimple) property, ((PropertyDefinitionSimple) propertyDefinition), strict); } return target; } private static Map<String, Object> getInnerMap(PropertyMap propertyMap, PropertyDefinitionMap propertyDefinition, boolean strict) { Map<String, Property> map = propertyMap.getMap(); Map<String,Object> result = new HashMap<String, Object>(map.size()); Set<String> names = map.keySet(); for (String name : names ) { Property property = map.get(name); PropertyDefinition definition = null; if (propertyDefinition != null) { definition = propertyDefinition.get(name); } Object target = convertProperty(property,definition, strict); result.put(name,target); } return result; } private static List<Object> getInnerList(PropertyList propertyList, PropertyDefinitionList definition, boolean strict) { List<Object> result = new ArrayList<Object>(propertyList.getList().size()); if (definition==null) { if (strict) { throw new IllegalArgumentException("No Definition exists for " + propertyList.getName()); } } PropertyDefinition memberDefinition = null; if (definition != null) { memberDefinition = definition.getMemberDefinition(); } for (Property property : propertyList.getList()) { Object target = convertProperty(property,memberDefinition, strict); result.add(target); } return result; } private static PropertyList getPropertyList(String propertyName, List<Object> objects) { PropertyList propertyList = new PropertyList(propertyName); Property target; for (Object o : objects) { if (o instanceof List) { // Not sure if we actually support that at all inside RHQ List list = (List) o; target = getPropertyList(propertyName,list); // TODO propertyName? } else if (o instanceof Map) { Map map = (Map) o; target = getPropertyMap(propertyName,map); // TODO propertyName? } else { target = new PropertySimple(propertyName,o); } propertyList.add(target); } return propertyList; } private static PropertyMap getPropertyMap(String propertyName, Map<String, Object> map) { PropertyMap propertyMap = new PropertyMap(propertyName); Set<String> keys = map.keySet(); for (String key : keys) { Object value = map.get(key); Property target; if (value instanceof Map) { target = getPropertyMap(key, (Map)value); } else if (value instanceof List) { target = getPropertyList(key, (List)value); } else { target = new PropertySimple(key,value); } propertyMap.put(target); } return propertyMap; } /** * Convert the passed simple property into an object of a matching type. The * type is determined with the help of the passed definition * @param property Property to convert * @param definition Definition of the Property * @return Object with the correct type */ public static Object convertSimplePropertyValue(PropertySimple property, PropertyDefinitionSimple definition, boolean strict) { if (definition == null && strict) { throw new IllegalArgumentException("No definition provided"); } if (property==null) { return null; } if (definition == null) { return property.getStringValue(); } PropertySimpleType type = definition.getType(); String val = property.getStringValue(); Object ret; switch (type) { case STRING: ret= val; break; case INTEGER: ret= Integer.valueOf(val); break; case BOOLEAN: ret= Boolean.valueOf(val); break; case LONG: ret= Long.valueOf(val); break; case FLOAT: ret= Float.valueOf(val); break; case DOUBLE: ret= Double.valueOf(val); break; default: ret= val; } return ret; } /** * Check that the passed configuration is valid wrt the passed definition * @param configuration A Configuration to check * @param definition A Definition to check the Configuration against * @return List of validation failure messages. List is empty if no errors were found. */ public static List<String> checkConfigurationWrtDefinition(Configuration configuration, ConfigurationDefinition definition) { List<String> messages = new ArrayList<String>(); if (configuration==null) { messages.add("Configuration is null"); } if (definition==null) { messages.add("Definition is null"); } if (configuration==null || definition==null) { return messages; } // Basic validation is done, now have a look at the properties for (PropertyDefinition propDef : definition.getPropertyDefinitions().values()) { String name = propDef.getName(); Property property = configuration.get(name); checkProperty(messages, propDef, property); } return messages; } /** * Recursively check the passed property against the passed property definition * @param messages Validation error messages are added here * @param propertyDefinition The definition to check against * @param property The property to check */ private static void checkProperty(List<String> messages, PropertyDefinition propertyDefinition, Property property) { String name = propertyDefinition.getName(); // If a property is required and not present we can bail out early if (propertyDefinition.isRequired() && property ==null) { messages.add("Required property [" + name + "] not found"); return; } // If a property is not required and is null, it is fine either if (!propertyDefinition.isRequired() && property==null) { return; } // Check if the property and definition are of the same kind (simple, map, list) boolean good = checkIfCompatible(propertyDefinition, property,messages); // We only need to do this dance if the kinds are matching if (good) { if (property instanceof PropertySimple) { checkDataTypeOfSimpleProperty((PropertyDefinitionSimple) propertyDefinition, (PropertySimple) property, messages); } else if (property instanceof PropertyList) { PropertyList propertyList = (PropertyList) property; PropertyDefinitionList propertyDefinitionList = (PropertyDefinitionList) propertyDefinition; for (Property prop : propertyList.getList()) { checkProperty(messages, propertyDefinitionList.getMemberDefinition(), prop); } } else if (property instanceof PropertyMap) { PropertyMap propertyMap = (PropertyMap) property; PropertyDefinitionMap propertyDefinitionMap = (PropertyDefinitionMap) propertyDefinition; for (Map.Entry<String,Property> entry : propertyMap.getMap().entrySet()) { Property prop = entry.getValue(); PropertyDefinition definition = propertyDefinitionMap.get(name); checkProperty(messages,definition,prop); } } } } /** * Check that for a Property that is defined with one of the non-string data types, the * stored value is actually valid according to this data type. * This also checks if a property is required, but its value is actually null. * * @param propDef Definition of the property, that contains the data type * @param property The property to check * @param messages Validation issues are added to this list. */ private static void checkDataTypeOfSimpleProperty(PropertyDefinitionSimple propDef, PropertySimple property, List<String> messages) { String prefix = "Property [" + property.getName() + "] is "; String val = property.getStringValue(); // If a property is not required and its value is null, we can just return if (!propDef.isRequired() && property.getStringValue()==null) { return; } // If a property is required and its value is null, we can just return if (propDef.isRequired() && property.getStringValue()==null) { messages.add(prefix + "required but was 'null'"); return; } switch (propDef.getType()) { case DOUBLE: try { Double.parseDouble(property.getStringValue()); } catch (NumberFormatException nfe ) { messages.add(prefix + "no double : " + val); } break; case FLOAT: float f; try { f = Float.parseFloat(property.getStringValue()); } catch (NumberFormatException nfe ) { messages.add(prefix + "no float : " + val); break; } if (f < Float.MIN_VALUE || f > Float.MAX_VALUE) { messages.add(prefix + "no valid float : " + val); } break; case INTEGER: try { Integer.parseInt(property.getStringValue()); } catch (NumberFormatException nfe ) { messages.add(prefix + "no integer : " + val); } break; case LONG: try { Long.parseLong(property.getStringValue()); } catch (NumberFormatException nfe ) { messages.add(prefix + "no long : " + val); } break; case BOOLEAN: String s = val.toLowerCase(); if (!(s.equals("true") || s.equals("false"))) { messages.add(prefix + "no boolean : " + val); } break; default: // Strings and long strings and directories and files } } /** * Check if the Kind of Definition and Property match. I.e. if a PropertyMap corresponds to a PropertyDefinitionMap * @param propDef PropertyDefinition to match * @param property Property to match with the definition * @param messages List of messages to add validation errors to. * @return true if the kinds are matching */ private static boolean checkIfCompatible(final PropertyDefinition propDef, final Property property, final List<String> messages) { boolean good = false ; if (propDef instanceof PropertyDefinitionSimple && property instanceof PropertySimple) { good = true; } else if (propDef instanceof PropertyDefinitionMap && property instanceof PropertyMap) { good = true; } else if (propDef instanceof PropertyDefinitionList && property instanceof PropertyList) { good = true; } if (!good) { String name = propDef.getName(); messages.add("The type of property for [" + name + "] does not match the definition"); } return good; } }