/* * Copyright 2010 Research Studios Austria Forschungsgesellschaft mBH * * This file is part of easyrec. * * easyrec 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, either version 3 of the License, or * (at your option) any later version. * * easyrec 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 easyrec. If not, see <http://www.gnu.org/licenses/>. */ package org.easyrec.plugin.configuration; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValues; import org.springframework.validation.BindingResult; import org.springframework.validation.DataBinder; import org.springframework.validation.Validator; import java.beans.PropertyEditor; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.*; /** * Provides utilities for setting and querying configuration parameters and their associated metadata as defined in the * {@link PluginParameter} annotations. * <p/> * Getting, setting and validation of parameter values is done by spring's {@link BeanWrapper} and {@link DataBinder} * classes. As the <code>DataBinder</code> maintains state over multiple calls to setValues(), we provide a reset() * method that re-initializes the <code>ConfigurationHelper</code>'s resources. * * @author fkleedorfer */ public class ConfigurationHelper { protected final Log logger = LogFactory.getLog(getClass()); protected Validator validator; protected Configuration configuration; protected BeanWrapper configurationWrapper; protected Map<String, Field> parameterFields = new HashMap<String, Field>(); private DataBinder dataBinder; public ConfigurationHelper(Configuration configuration) { this.configuration = configuration; init(); } /** * Re-initializes the internally used resources of this configuration helper. If this method is not called between * multiple calls of setValues(), the <code>BindingResult</code> accumulates errors. */ public void reset() { init(); } /* * (non-Javadoc) * * @see org.easyrec.plugin.container.ConfigurationHelper#getParameterDescription (java.lang.String) */ public String getParameterDescription(String parameterName) { PluginParameter parameterAnnotation = getParameterAnnotation(parameterName); return parameterAnnotation.description(); } public String getParameterDisplayName(String parameterName) { PluginParameter parameterAnnotation = getParameterAnnotation(parameterName); return parameterAnnotation.displayName(); } public Set<String> getParameterNames() { return Collections.unmodifiableSet(this.parameterFields.keySet()); } public boolean getParameterOptional(String parameterName) { PluginParameter parameterAnnotation = getParameterAnnotation(parameterName); return parameterAnnotation.optional(); } public String getParameterShortDescription(String parameterName) { PluginParameter parameterAnnotation = getParameterAnnotation(parameterName); return parameterAnnotation.shortDescription(); } public int getParameterDisplayOrder(String parameterName) { PluginParameter parameterAnnotation = getParameterAnnotation(parameterName); return parameterAnnotation.displayOrder(); } public boolean getParameterAsTextArea(String parameterName) { PluginParameter parameterAnnotation = getParameterAnnotation(parameterName); return parameterAnnotation.asTextArea(); } /** * Converts the parameter value to a string. If a custom {@link PropertyEditor} is configured for the parameter (via * the {@link PluginParameterPropertyEditor} Annotation), it is used for conversion, otherwise the value's * <code>toString()</code> method is called. If the value is <code>null</code>, <code>null</code> is returned. * * @param parameterName * @return * @throws IllegalArgumentException if no parameter of given name exists. */ public String getParameterStringValue(String parameterName) { Field field = getParameterField(parameterName); PropertyEditor editor = this.configurationWrapper.findCustomEditor(field.getType(), field.getName()); Object value = getParameterValue(parameterName); if (editor != null) { editor.setValue(value); return editor.getAsText(); } else { if (value == null) { return null; } return value.toString(); } } /** * Returns the value for the specified parameter name. * * @param parameterName * @return */ public Object getParameterValue(String parameterName) { return this.configurationWrapper.getPropertyValue(parameterName); } /** * Returns all values as a {@link PropertyValues} object, using no property key prefix. * * @return */ public PropertyValues getValues() { return getValues(null); } /** * Same as getValues(), but property keys are prefixed with the specified prefix. * * @param propertyKeyPrefix * @return */ public PropertyValues getValues(String propertyKeyPrefix) { if (propertyKeyPrefix == null) { propertyKeyPrefix = ""; } else { propertyKeyPrefix += "."; } MutablePropertyValues props = new MutablePropertyValues(); for (String propertyName : getParameterNames()) { String strVal = getParameterStringValue(propertyName); if (strVal != null) { props.addPropertyValue(propertyKeyPrefix + propertyName, strVal); } } return props; } /** * Delegates the call to <code>getValuesAsProperties(null,null)</code>. * * @return */ public Properties getValuesAsProperties() { return getValuesAsProperties(null, null); } /** * Delegates the call to <code>getValuesAsProperties(props,null)</code> * * @param props * @return */ public Properties getValuesAsProperties(Properties props) { return getValuesAsProperties(props, null); } /** * Populate the specified properties object with the configuration values, using the specified prefix for the * property keys. If a parameter value is null, no property is created for it. * * @param props properties to populate. New Properties are created if null. * @param propertyKeyPrefix prefix for property keys. Use null to omit prefix. * @return */ public Properties getValuesAsProperties(Properties props, String propertyKeyPrefix) { if (props == null) { props = new Properties(); } if (propertyKeyPrefix == null) { propertyKeyPrefix = ""; } else { propertyKeyPrefix += "."; } for (String propertyName : getParameterNames()) { String strVal = getParameterStringValue(propertyName); if (strVal != null) { props.setProperty(propertyKeyPrefix + propertyName, strVal); } } return props; } /** * Delegates the call to <code>getValuesAsProperties(null,propertyKeyPrefix)</code> * * @param propertyKeyPrefix * @return */ public Properties getValuesAsProperties(String propertyKeyPrefix) { return getValuesAsProperties(null, propertyKeyPrefix); } /** * Sets the parameter values from the specified PropertyValues object. A parameter value is set to <code>null</code> * if the respective property is not present. * * @param values * @return */ public BindingResult setValues(PropertyValues values) { this.dataBinder.bind(values); try { this.dataBinder.validate(); } catch (RuntimeException ex) { logger.warn("Plugin configuration validator threw an exception!", ex); } return this.dataBinder.getBindingResult(); } /** * Sets the parameter values from the specified Properties object. A parameter value is set to <code>null</code> if * the respective property is not present. * * @param properties * @param propertyKeyPrefix * @return */ public BindingResult setValues(Properties properties, String propertyKeyPrefix) { if (propertyKeyPrefix == null) { propertyKeyPrefix = ""; } else { propertyKeyPrefix += "."; } MutablePropertyValues propertyValues = new MutablePropertyValues(); for (String propertyName : getParameterNames()) { propertyValues.addPropertyValue(propertyName, properties.getProperty(propertyKeyPrefix + propertyName)); } return setValues(propertyValues); } /** * Returns the PluginParameter annotation for the specified name. * * @param parameterName * @return */ protected PluginParameter getParameterAnnotation(String parameterName) { Field field = getParameterField(parameterName); return field.getAnnotation(PluginParameter.class); } /** * Retrieves the {@link Field} with the given name. If no Field is found in the <code>Configuration</code> class of * this <code>ConfigurationWrapper</code> that is annotated with the {@link PluginParameter} annotation, an * {@link IllegalArgumentException} is raised. * * @param parameterName * @return * @throws IllegalArgumentException if no parameter of the specified name is found. */ protected Field getParameterField(String parameterName) { Field field = this.parameterFields.get(parameterName); if (field == null) { throw new IllegalArgumentException("No field named '" + parameterName + "' with the field-level annotation 'PluginParameter' in class '" + this.configuration.getClass().getName() + "'"); } return field; } protected void init() { this.configurationWrapper = new BeanWrapperImpl(this.configuration); this.parameterFields = getParameterFields(this.configuration); this.dataBinder = new DataBinder(this.configuration, "Configuration"); allowFieldsForDataBinding(); registerPropertyEditors(); checkForValidator(); } private void allowFieldsForDataBinding() { Set<String> propertyNames = getParameterNames(); this.dataBinder.setAllowedFields(propertyNames.toArray(new String[propertyNames.size()])); } private void checkForValidator() { PluginConfigurationValidator validatorAnnotation = this.configuration.getClass() .getAnnotation(PluginConfigurationValidator.class); if (validatorAnnotation != null) { Validator validator; try { validator = validatorAnnotation.validatorClass().getConstructor().newInstance(); } catch (Exception e) { throw new IllegalArgumentException("could not create validator for configuration", e); } this.dataBinder.setValidator(validator); this.validator = validator; } } private Set<Field> collectFields(Class<?> clazz) { return collectFields(clazz, null); } private Set<Field> collectFields(Class<?> clazz, Set<Field> fields) { if (fields == null) { fields = new HashSet<Field>(); } Field[] curFields = clazz.getDeclaredFields(); for (int i = 0; i < curFields.length; i++) { fields.add(curFields[i]); } Class<?> superClass = clazz.getSuperclass(); if (superClass != null) { return collectFields(superClass, fields); } return fields; } private Map<String, Field> getParameterFields(Configuration config) { Set<Field> fields = collectFields(config.getClass()); Map<String, Field> parameterFields = new HashMap<String, Field>(); for (Field field : fields) { Annotation pluginParameterAnnotation = field.getAnnotation(PluginParameter.class); if (pluginParameterAnnotation != null) { parameterFields.put(field.getName(), field); } } return parameterFields; } private void registerPropertyEditors() { for (String fieldName : this.parameterFields.keySet()) { Field field = this.parameterFields.get(fieldName); PluginParameterPropertyEditor propertyEditorAnnotation = field .getAnnotation(PluginParameterPropertyEditor.class); if (propertyEditorAnnotation != null) { PropertyEditor editor; try { editor = propertyEditorAnnotation.propertyEditorClass().getConstructor().newInstance(); } catch (Exception e) { throw new IllegalArgumentException( "could not create property editor for field '" + field.getName() + "'", e); } this.dataBinder.registerCustomEditor(field.getType(), field.getName(), editor); this.configurationWrapper.registerCustomEditor(field.getType(), field.getName(), editor); } } } }