/*
* 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.core.clientapi.agent.metadata;
import static org.rhq.core.domain.configuration.definition.ConfigurationFormat.RAW;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.xml.bind.JAXBElement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.rhq.core.clientapi.agent.configuration.ConfigurationUtility;
import org.rhq.core.clientapi.descriptor.configuration.ConfigurationDescriptor;
import org.rhq.core.clientapi.descriptor.configuration.ConfigurationProperty;
import org.rhq.core.clientapi.descriptor.configuration.ConfigurationTemplateDescriptor;
import org.rhq.core.clientapi.descriptor.configuration.ConstraintType;
import org.rhq.core.clientapi.descriptor.configuration.DynamicProperty;
import org.rhq.core.clientapi.descriptor.configuration.ExpressionScope;
import org.rhq.core.clientapi.descriptor.configuration.FloatConstraintType;
import org.rhq.core.clientapi.descriptor.configuration.IntegerConstraintType;
import org.rhq.core.clientapi.descriptor.configuration.ListProperty;
import org.rhq.core.clientapi.descriptor.configuration.MapProperty;
import org.rhq.core.clientapi.descriptor.configuration.Option;
import org.rhq.core.clientapi.descriptor.configuration.OptionSource;
import org.rhq.core.clientapi.descriptor.configuration.PropertyGroup;
import org.rhq.core.clientapi.descriptor.configuration.PropertyOptions;
import org.rhq.core.clientapi.descriptor.configuration.PropertyType;
import org.rhq.core.clientapi.descriptor.configuration.RegexConstraintType;
import org.rhq.core.clientapi.descriptor.configuration.SimpleProperty;
import org.rhq.core.domain.configuration.AbstractPropertyMap;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertyDefinitionDynamic;
import org.rhq.core.domain.configuration.PropertyDynamicType;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ActivationPolicy;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.ConfigurationTemplate;
import org.rhq.core.domain.configuration.definition.PropertyDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionEnumeration;
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.PropertyGroupDefinition;
import org.rhq.core.domain.configuration.definition.PropertyOptionsSource;
import org.rhq.core.domain.configuration.definition.PropertySimpleType;
import org.rhq.core.domain.configuration.definition.constraint.Constraint;
import org.rhq.core.domain.configuration.definition.constraint.FloatRangeConstraint;
import org.rhq.core.domain.configuration.definition.constraint.IntegerRangeConstraint;
import org.rhq.core.domain.configuration.definition.constraint.RegexConstraint;
import org.rhq.core.domain.util.StringUtils;
/**
* @author Jason Dobies
* @author Ian Springer
*/
public class ConfigurationMetadataParser {
private static Log log = LogFactory.getLog("ConfigurationMetadataParser");
public static ConfigurationDefinition parse(@NotNull String configurationName, ConfigurationDescriptor descriptor)
throws InvalidPluginDescriptorException {
if (descriptor == null) {
return null;
}
if (configurationName == null) {
throw new IllegalArgumentException("ConfigurationName must not be null");
}
ConfigurationDefinition configurationDefinition = new ConfigurationDefinition(configurationName,
descriptor.getNotes());
configurationDefinition.setConfigurationFormat(getConfigurationFormat(descriptor));
if (configurationDefinition.getConfigurationFormat() == RAW) {
return configurationDefinition;
}
for (ConfigurationTemplateDescriptor templateDescriptor : descriptor.getTemplate()) {
configurationDefinition.putTemplate(parseTemplate(templateDescriptor));
}
ConfigurationTemplate defaultTemplate = initDefaultTemplate(configurationDefinition);
configurationDefinition.putTemplate(defaultTemplate);
Configuration defaultConfiguration = defaultTemplate.getConfiguration();
parseProperties(descriptor, defaultConfiguration, configurationDefinition);
ConfigurationUtility.normalizeConfiguration(defaultConfiguration, configurationDefinition);
return configurationDefinition;
}
private static org.rhq.core.domain.configuration.definition.ConfigurationFormat getConfigurationFormat(
ConfigurationDescriptor descriptor) {
if (descriptor.getConfigurationFormat() == null) {
return org.rhq.core.domain.configuration.definition.ConfigurationFormat.STRUCTURED;
}
switch (descriptor.getConfigurationFormat()) {
case STRUCTURED:
return org.rhq.core.domain.configuration.definition.ConfigurationFormat.STRUCTURED;
case RAW:
return org.rhq.core.domain.configuration.definition.ConfigurationFormat.RAW;
default:
return org.rhq.core.domain.configuration.definition.ConfigurationFormat.STRUCTURED_AND_RAW;
}
}
private static ConfigurationTemplate initDefaultTemplate(ConfigurationDefinition configurationDefinition) {
ConfigurationTemplate defaultTemplate = configurationDefinition.getDefaultTemplate();
if (defaultTemplate == null) {
// TODO: Not everything should have a default template... only stuff that has default values
defaultTemplate = new ConfigurationTemplate(ConfigurationTemplate.DEFAULT_TEMPLATE_NAME,
"the default template");
Configuration defaultConfiguration = new Configuration();
defaultTemplate.setConfiguration(defaultConfiguration);
}
defaultTemplate.setDefault(true);
return defaultTemplate;
}
private static void parseProperties(ConfigurationDescriptor descriptor, Configuration defaultConfiguration,
ConfigurationDefinition configurationDefinition) throws InvalidPluginDescriptorException {
List<PropertyGroup> groups = descriptor.getGroup();
List<JAXBElement<? extends ConfigurationProperty>> properties = descriptor.getConfigurationProperty();
if ((groups.size() == 0) && (properties.size() == 0)) {
throw new InvalidPluginDescriptorException(
"Configuration properties are missing. Resource configurations must have at least one group or one config-property as a child.");
}
int groupOrderIndex = 0;
for (PropertyGroup group : descriptor.getGroup()) {
org.rhq.core.domain.configuration.definition.PropertyGroupDefinition groupDef = new PropertyGroupDefinition(
group.getName());
groupDef.setDisplayName((group.getDisplayName() != null) ? group.getDisplayName() : StringUtils
.deCamelCase(group.getName()));
groupDef.setDescription(group.getDescription());
groupDef.setDefaultHidden(group.isHiddenByDefault());
groupDef.setOrder(groupOrderIndex++);
List<JAXBElement<? extends ConfigurationProperty>> groupProperties = group.getConfigurationProperty();
int propertyOrderIndex = 0;
for (JAXBElement<? extends ConfigurationProperty> jaxbProperty : groupProperties) {
ConfigurationProperty uncastedProperty = jaxbProperty.getValue();
PropertyDefinition propertyDefinition = parseProperty(uncastedProperty, propertyOrderIndex,
defaultConfiguration);
if (configurationDefinition != null) {
propertyDefinition.setPropertyGroupDefinition(groupDef);
configurationDefinition.put(propertyDefinition);
propertyOrderIndex++;
}
}
}
int propertyOrderIndex = 0;
for (JAXBElement<? extends ConfigurationProperty> jaxbProperty : properties) {
ConfigurationProperty uncastedProperty = jaxbProperty.getValue();
PropertyDefinition propertyDefinition = parseProperty(uncastedProperty, propertyOrderIndex,
defaultConfiguration);
if (configurationDefinition != null) {
configurationDefinition.put(propertyDefinition);
propertyOrderIndex++;
}
}
}
private static ConfigurationTemplate parseTemplate(ConfigurationTemplateDescriptor templateDescriptor)
throws InvalidPluginDescriptorException {
ConfigurationTemplate template = new ConfigurationTemplate(templateDescriptor.getName(),
templateDescriptor.getDescription());
Configuration templateConfiguration = new Configuration();
template.setConfiguration(templateConfiguration);
parseProperties(templateDescriptor, templateConfiguration, null);
return template;
}
private static PropertyDefinition parseProperty(ConfigurationProperty uncastedProperty, int orderIndex)
throws InvalidPluginDescriptorException {
return parseProperty(uncastedProperty, orderIndex, null);
}
private static PropertyDefinition parseProperty(ConfigurationProperty uncastedProperty, int orderIndex,
AbstractPropertyMap defaultConfigurationParentMap) throws InvalidPluginDescriptorException {
PropertyDefinition property = null;
if (uncastedProperty instanceof SimpleProperty) {
property = parseSimpleProperty((SimpleProperty) uncastedProperty, defaultConfigurationParentMap);
} else if (uncastedProperty instanceof ListProperty) {
property = parseListProperty((ListProperty) uncastedProperty);
} else if (uncastedProperty instanceof MapProperty) {
property = parseMapProperty((MapProperty) uncastedProperty, defaultConfigurationParentMap);
} else if (uncastedProperty instanceof DynamicProperty) {
property = parseDynamicProperty((DynamicProperty) uncastedProperty);
}
if (property != null) {
property.setOrder(orderIndex);
}
return property;
}
private static PropertyDefinitionSimple parseSimpleProperty(SimpleProperty simpleProperty,
AbstractPropertyMap defaultConfigurationParentMap) throws InvalidPluginDescriptorException {
String description = parseMultiValue(simpleProperty.getDescription(), simpleProperty.getLongDescription());
String displayName = (simpleProperty.getDisplayName() != null) ? simpleProperty.getDisplayName() : StringUtils
.deCamelCase(simpleProperty.getName());
PropertyDefinitionSimple property = new PropertyDefinitionSimple(simpleProperty.getName(), description,
simpleProperty.isRequired(), translatePropertyType(simpleProperty.getType()));
property.setReadOnly(simpleProperty.isReadOnly());
property.setSummary(simpleProperty.isSummary());
property.setActivationPolicy(translateActivationPolicy(simpleProperty.getActivationPolicy()));
property.setConstraints(translateContraints(simpleProperty.getConstraint()));
property.setDisplayName(displayName);
property.setDefaultValue(simpleProperty.getDefaultValue());
property.setUnits(MetricsMetadataParser.getMeasurementUnits(simpleProperty.getUnits(), null));
String initialValue = simpleProperty.getInitialValue();
if ((defaultConfigurationParentMap != null) && (initialValue != null)) {
defaultConfigurationParentMap.put(new PropertySimple(simpleProperty.getName(), initialValue));
}
// Load the enumeration of options
if (simpleProperty.getPropertyOptions() != null) {
parsePropertyOptions(property, simpleProperty.getPropertyOptions());
}
if (simpleProperty.getOptionSource() != null) {
PropertyOptionsSource optionsSource = new PropertyOptionsSource();
OptionSource source = simpleProperty.getOptionSource();
optionsSource.setTarget(source.getTarget().value());
optionsSource.setLinkToTarget(source.isLinkToTarget());
if (source.getFilter() != null && source.getFilter().length() > 40) {
throw new IllegalArgumentException("Filter expression must be less than 40 chars long");
}
optionsSource.setFilter(source.getFilter());
ExpressionScope expressionScope = source.getExpressionScope();
if (expressionScope != null) {
optionsSource.setExpressionScope(PropertyOptionsSource.ExpressionScope.fromValue(expressionScope
.value()));
}
String expression = source.getExpression();
if (expression == null || expression.isEmpty())
throw new IllegalArgumentException("Expression must not be empty");
if (expression.length() > 400)
throw new IllegalArgumentException("Expression must be less than 400 chars long");
optionsSource.setExpression(expression);
property.setOptionsSource(optionsSource);
}
return property;
}
private static PropertyDefinitionDynamic parseDynamicProperty(DynamicProperty dynamicProperty)
throws InvalidPluginDescriptorException {
String description = parseMultiValue(dynamicProperty.getDescription(), dynamicProperty.getLongDescription());
String displayName = (dynamicProperty.getDisplayName() != null) ? dynamicProperty.getDisplayName()
: StringUtils.deCamelCase(dynamicProperty.getName());
PropertyDefinitionDynamic property = new PropertyDefinitionDynamic(dynamicProperty.getName(), description,
dynamicProperty.isRequired(), PropertyDynamicType.DATABASE, dynamicProperty.getDatabaseBacking().getKey()
.value());
property.setReadOnly(dynamicProperty.isReadOnly());
property.setSummary(dynamicProperty.isSummary());
property.setDisplayName(displayName);
property.setActivationPolicy(translateActivationPolicy(dynamicProperty.getActivationPolicy()));
return property;
}
private static List<PropertyDefinitionEnumeration> parsePropertyOptions(PropertyDefinitionSimple parentProperty,
PropertyOptions options) {
List<PropertyDefinitionEnumeration> results = new ArrayList<PropertyDefinitionEnumeration>();
for (Option option : options.getOption()) {
String name = option.getName();
if (name == null) {
name = option.getValue();
}
PropertyDefinitionEnumeration enumeration = new PropertyDefinitionEnumeration(name, option.getValue());
parentProperty.addEnumeratedValues(enumeration);
}
parentProperty.setAllowCustomEnumeratedValue(options.isAllowCustomValue());
return results;
}
private static PropertyDefinitionList parseListProperty(ListProperty listProperty)
throws InvalidPluginDescriptorException {
String description = parseMultiValue(listProperty.getDescription(), listProperty.getLongDescription());
JAXBElement<? extends ConfigurationProperty> memberProperty = listProperty.getConfigurationProperty();
PropertyDefinition memberDefinition = (memberProperty != null) ? parseProperty(memberProperty.getValue(), 0)
: null;
PropertyDefinitionList list = new PropertyDefinitionList(listProperty.getName().intern(), description,
listProperty.isRequired(), memberDefinition);
String displayName = (listProperty.getDisplayName() != null) ? listProperty.getDisplayName() : StringUtils
.deCamelCase(listProperty.getName());
if (displayName != null) {
list.setDisplayName(displayName.intern());
}
list.setReadOnly(listProperty.isReadOnly());
list.setSummary(listProperty.isSummary());
list.setMin(listProperty.getMin().intValue());
if (listProperty.getMax().equals("unbounded")) {
list.setMax(Integer.MAX_VALUE);
} else {
list.setMax(Integer.parseInt(listProperty.getMax()));
}
return list;
}
private static PropertyDefinitionMap parseMapProperty(MapProperty mapProperty,
AbstractPropertyMap defaultConfigurationParentMap) throws InvalidPluginDescriptorException {
String description = parseMultiValue(mapProperty.getDescription(), mapProperty.getLongDescription());
PropertyDefinitionMap propDefMap = new PropertyDefinitionMap(mapProperty.getName().intern(), description,
mapProperty.isRequired());
String displayName = (mapProperty.getDisplayName() != null) ? mapProperty.getDisplayName() : StringUtils
.deCamelCase(mapProperty.getName());
if (displayName != null) {
propDefMap.setDisplayName(displayName.intern());
}
propDefMap.setReadOnly(mapProperty.isReadOnly());
propDefMap.setSummary(mapProperty.isSummary());
// Add an instance of the map to the default config, if appropriate.
PropertyMap propMap;
if (defaultConfigurationParentMap != null) {
propMap = new PropertyMap(propDefMap.getName());
defaultConfigurationParentMap.put(propMap);
} else {
propMap = null;
}
// Process the map's nested properties.
List<JAXBElement<? extends ConfigurationProperty>> nestedProperties = mapProperty.getConfigurationProperty();
int propertyOrderIndex = 0;
for (JAXBElement<? extends ConfigurationProperty> jaxbProperty : nestedProperties) {
ConfigurationProperty uncastedProperty = jaxbProperty.getValue();
PropertyDefinition propertyDefinition = parseProperty(uncastedProperty, propertyOrderIndex++, propMap);
propDefMap.put(propertyDefinition);
}
return propDefMap;
}
private static PropertySimpleType translatePropertyType(PropertyType fromType)
throws InvalidPluginDescriptorException {
PropertySimpleType toType;
switch (fromType) {
case BOOLEAN: {
toType = PropertySimpleType.BOOLEAN;
break;
}
case DIRECTORY: {
toType = PropertySimpleType.DIRECTORY;
break;
}
case FILE: {
toType = PropertySimpleType.FILE;
break;
}
case FLOAT: {
toType = PropertySimpleType.FLOAT;
break;
}
case DOUBLE: {
toType = PropertySimpleType.DOUBLE;
break;
}
case INTEGER: {
toType = PropertySimpleType.INTEGER;
break;
}
case LONG: {
toType = PropertySimpleType.LONG;
break;
}
case LONG_STRING: {
toType = PropertySimpleType.LONG_STRING;
break;
}
case PASSWORD: {
toType = PropertySimpleType.PASSWORD;
break;
}
case STRING: {
toType = PropertySimpleType.STRING;
break;
}
default: {
throw new InvalidPluginDescriptorException(
"Property type specified does not have a corresponding domain property type. Property type: "
+ fromType);
}
}
return toType;
}
private static ActivationPolicy translateActivationPolicy(
org.rhq.core.clientapi.descriptor.configuration.ActivationPolicy fromPolicy)
throws InvalidPluginDescriptorException {
ActivationPolicy toPolicy;
switch (fromPolicy) {
case IMMEDIATE: {
toPolicy = ActivationPolicy.IMMEDIATE;
break;
}
case RESTART: {
toPolicy = ActivationPolicy.RESTART;
break;
}
case SHUTDOWN: {
toPolicy = ActivationPolicy.SHUTDOWN;
break;
}
default: {
throw new InvalidPluginDescriptorException(
"Activation policy specified does not have a corresponding domain activation policy type. Activation Policy type: "
+ fromPolicy);
}
}
return toPolicy;
}
private static Set<Constraint> translateContraints(List<ConstraintType> fromConstraints)
throws InvalidPluginDescriptorException {
Set<Constraint> toConstraints = new HashSet<Constraint>();
if (fromConstraints == null) {
return toConstraints;
}
for (ConstraintType fromC : fromConstraints) {
List<Object> constraints = fromC.getIntegerConstraintOrFloatConstraintOrRegexConstraint();
for (Object constraint : constraints) {
if (constraint instanceof FloatConstraintType) {
FloatConstraintType floatDetails = (FloatConstraintType) constraint;
Double floatMin = (floatDetails.getMinimum() != null) ? new Double(floatDetails.getMinimum()
.toString()) : null;
Double floatMax = (floatDetails.getMaximum() != null) ? new Double(floatDetails.getMaximum()
.toString()) : null;
FloatRangeConstraint fc = new FloatRangeConstraint(floatMin, floatMax);
toConstraints.add(fc);
} else if (constraint instanceof IntegerConstraintType) {
IntegerConstraintType intDetails = (IntegerConstraintType) constraint;
Long longMin = (intDetails.getMinimum() != null) ? new Long(intDetails.getMinimum().toString())
: null;
Long longMax = (intDetails.getMaximum() != null) ? new Long(intDetails.getMaximum().toString())
: null;
IntegerRangeConstraint ic = new IntegerRangeConstraint(longMin, longMax);
toConstraints.add(ic);
} else if (constraint instanceof RegexConstraintType) {
RegexConstraintType regexDetails = (RegexConstraintType) constraint;
RegexConstraint rc = new RegexConstraint();
rc.setDetails(regexDetails.getExpression());
toConstraints.add(rc);
} else {
// this will only occur if we change the .xsd schema and add a type but forget to add to the code above
throw new InvalidPluginDescriptorException("Unknown constraint type: " + fromC);
}
}
}
return toConstraints;
}
/**
* Iterates over each value specified. For the first non-null value found, the value is trimmed and returned. If no
* non-null values are found, <code>null</code> is returned.
*
* @param values values, in order of priority, to be checked.
*
* @return trimmed value if one is found; <code>null</code> if none are found.
*/
private static String parseMultiValue(String... values) {
String value = null;
for (String s : values) {
if (s == null) {
continue;
}
value = s;
break;
}
return (value != null) ? value.trim() : null;
}
}