org.jboss.arquillian.drone.configuration.ConfigurationMapper Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2011, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.arquillian.drone.configuration;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.arquillian.config.descriptor.api.ArquillianDescriptor;
import org.jboss.arquillian.config.descriptor.api.ExtensionDef;
import org.jboss.arquillian.core.spi.Validate;
import org.jboss.arquillian.drone.configuration.legacy.LegacyConfigurationMapper;
import org.jboss.arquillian.drone.configuration.mapping.BooleanValueMapper;
import org.jboss.arquillian.drone.configuration.mapping.DoubleValueMapper;
import org.jboss.arquillian.drone.configuration.mapping.FileValueMapper;
import org.jboss.arquillian.drone.configuration.mapping.IntegerValueMapper;
import org.jboss.arquillian.drone.configuration.mapping.LogLevelMapper;
import org.jboss.arquillian.drone.configuration.mapping.LongValueMapper;
import org.jboss.arquillian.drone.configuration.mapping.StringValueMapper;
import org.jboss.arquillian.drone.configuration.mapping.URIValueMapper;
import org.jboss.arquillian.drone.configuration.mapping.URLValueMapper;
import org.jboss.arquillian.drone.configuration.mapping.ValueMapper;
import org.jboss.arquillian.drone.spi.DroneConfiguration;
/**
* Utility which maps Arquillian Descriptor to a Drone configuration.
*
* Configuration mapper does inspect a configuration for available fields and it tries to fill the values according to
* what is
* provided in arquillian.xml or in system properties.
*
* All properties, which does not have an appropriate fields to be assigned, are stored in each available map, given that
* configuration provides a {@code Map} fields. Properties using their name as a key.
*
* @author Karel Piwko
* @see DroneConfiguration
*/
public class ConfigurationMapper {
// FIXME this should be in SPI with a proper event model
public static final List> VALUE_MAPPERS;
private static final Logger log = Logger.getLogger(ConfigurationMapper.class.getName());
static {
VALUE_MAPPERS = new ArrayList>();
VALUE_MAPPERS.add(BooleanValueMapper.INSTANCE);
VALUE_MAPPERS.add(DoubleValueMapper.INSTANCE);
VALUE_MAPPERS.add(IntegerValueMapper.INSTANCE);
VALUE_MAPPERS.add(LongValueMapper.INSTANCE);
VALUE_MAPPERS.add(StringValueMapper.INSTANCE);
VALUE_MAPPERS.add(URIValueMapper.INSTANCE);
VALUE_MAPPERS.add(URLValueMapper.INSTANCE);
VALUE_MAPPERS.add(FileValueMapper.INSTANCE);
VALUE_MAPPERS.add(LogLevelMapper.INSTANCE);
}
// FIXME this should not be a static helper class but a proper observer on ArquillianDescriptor
private ConfigurationMapper() {
throw new InstantiationError();
}
/**
* Maps a configuration using Arquillian Descriptor file
*
* @param
* Type of the configuration
* @param descriptor
* Arquillian Descriptor
* @param configuration
* Configuration object
* @param qualifier
* Qualifier annotation
*
* @return Configured configuration
*/
public static > T fromArquillianDescriptor(ArquillianDescriptor descriptor,
T configuration, Class extends Annotation> qualifier) {
Validate.notNull(descriptor, "Descriptor must not be null");
Validate.notNull(configuration, "Configuration object must not be null");
Validate.notNull(qualifier, "Qualifier object must not be null");
String descriptorQualifier = configuration.getConfigurationName();
String qualifierName = qualifier.getSimpleName().toLowerCase();
Map nameValuePairs = loadNameValuePairs(descriptor, descriptorQualifier, qualifierName);
// ARQ-1882
Map sanitizedNameValuePairs = new HashMap(nameValuePairs.size());
for (Map.Entry entry : nameValuePairs.entrySet()) {
if (entry.getKey() != null) {
sanitizedNameValuePairs.put(entry.getKey(), entry.getValue());
}
}
return mapFromNameValuePairs(configuration, sanitizedNameValuePairs);
}
/**
* Maps configuration values from Arquillian Descriptor
*
* @param
* A type of configuration
* @param configuration
* Configuration object
*
* @return Configured configuration of given type
*/
// @SuppressWarnings("unchecked")
static > T mapFromNameValuePairs(T configuration,
Map nameValuePairs) {
Map fields = SecurityActions.getAccessableFields(configuration.getClass());
// extract all Map in the configuration and initialize them
List maps = SecurityActions.getMapFields(configuration.getClass(), String.class, Object.class);
for (Field mapField : maps) {
try {
// get or create a map
@SuppressWarnings("unchecked")
Map map = (Map) mapField.get(configuration);
if (map == null) {
map = new HashMap();
}
mapField.set(configuration, map);
} catch (Exception e) {
throw new RuntimeException("Could not map Drone configuration(" + configuration.getConfigurationName()
+ ") for " + configuration.getClass().getName() + " from Arquillian Descriptor", e);
}
}
// map basic fields
for (Map.Entry nameValue : nameValuePairs.entrySet()) {
String name = nameValue.getKey();
String reversedName = keyTransformReverse(name);
// map a field which has a field directly available in the configuration
if (fields.containsKey(name)) {
injectField(configuration, maps, fields, name, nameValue.getValue());
}
// map a field which comes from a system property which has a field available in the configuration
// note, due to multiple deprecation, it might be possible that field we deprecated in favor of capability
// has reversed name value exactly the same as capability - ARQ-1638
else if (fields.containsKey(reversedName) && !LegacyConfigurationMapper.isLegacy(reversedName)) {
// we prefer new format arquillian.mockdriver.intField over arquillian.mockdriver.int.field
log.log(Level.WARNING,
"The system property \"{0}\" used in Arquillian \"{1}\" configuration is deprecated, please rather use new format \"{2}\"",
new Object[] {name, configuration.getConfigurationName(), keyTransformReverse(name)});
injectField(configuration, maps, fields, keyTransformReverse(name), nameValue.getValue());
}
// map a field which does not have this luck into all available maps in configuration
else {
injectMapProperty(configuration, maps, fields, name, nameValue.getValue());
}
}
return configuration;
}
/**
* Parses Arquillian Descriptor into property name - value pairs value
*
* @param descriptor
* An Arquillian Descriptor
* @param descriptorQualifier
* A qualifier used for extension configuration in the descriptor
* @param qualifierName
* Name of the qualifier passed
*/
static Map loadNameValuePairs(ArquillianDescriptor descriptor, String descriptorQualifier,
String qualifierName) {
String fullDescriptorQualifier =
new StringBuilder(descriptorQualifier).append("-").append(qualifierName).toString();
ExtensionDef match = null;
for (ExtensionDef extension : descriptor.getExtensions()) {
if (fullDescriptorQualifier.equals(extension.getExtensionName())) {
Map nameValuePairs = extension.getExtensionProperties();
if (log.isLoggable(Level.FINE)) {
log.fine(
"Using for Drone Configuration");
}
return nameValuePairs;
} else if (descriptorQualifier.equals(extension.getExtensionName())) {
match = extension;
}
}
// found generic only
if (match != null) {
Map nameValuePairs = match.getExtensionProperties();
if (log.isLoggable(Level.FINE)) {
log.fine("Using for Drone Configuration");
}
return nameValuePairs;
}
return Collections.emptyMap();
}
/**
* Maps a property key to a field name.
*
* Replaces dot ('.') and lower case character with an upper case character
*
* @param propertyName
* The name of field
*
* @return Corresponding field name
*/
static String keyTransformReverse(String propertyName) {
StringBuilder sb = new StringBuilder();
boolean upperCaseFlag = false;
for (int i = 0; i < propertyName.length(); i++) {
char c = propertyName.charAt(i);
if (c == '.') {
upperCaseFlag = true;
} else if (upperCaseFlag && Character.isLowerCase(c)) {
sb.append(Character.toUpperCase(c));
upperCaseFlag = false;
} else {
sb.append(c);
}
}
return sb.toString();
}
static > Field injectField(T configuration, List maps,
Map fields,
String fieldName, String value) {
try {
Field f = fields.get(fieldName);
if (f.getAnnotation(Deprecated.class) != null) {
log.log(Level.WARNING, "The property \"{0}\" used in Arquillian \"{1}\" configuration is deprecated.",
new Object[] {f.getName(), configuration.getConfigurationName()});
}
// remap the property into capability if this is a legacy one
// or remap the property into different property field
if (LegacyConfigurationMapper.isLegacy(fieldName)) {
String newKey = LegacyConfigurationMapper.remapKey(fieldName);
String newValue = LegacyConfigurationMapper.remapValue(fieldName, value);
if (LegacyConfigurationMapper.remapsToCapability(fieldName)) {
injectMapProperty(configuration, maps, fields, newKey, newValue);
} else {
injectField(configuration, maps, fields, newKey, newValue);
}
}
f.set(configuration, convert(f.getType(), value));
return f;
} catch (Exception e) {
throw new RuntimeException(
"Could not map Drone configuration(" + configuration.getConfigurationName() + ") for "
+ configuration.getClass().getName() + " from Arquillian Descriptor", e);
}
}
static > void injectMapProperty(T configuration, List maps,
Map fields, String propertyName, String value) {
try {
for (Field mapField : maps) {
Object typedValue = value;
if (CapabilityTypeMapper.isCastNeeded(propertyName)) {
typedValue = CapabilityTypeMapper.createTypedObjectFromString(propertyName, value);
}
// put property into a map
@SuppressWarnings("unchecked")
Map map = (Map) mapField.get(configuration);
map.put(propertyName, typedValue);
}
} catch (Exception e) {
throw new RuntimeException(
"Could not map Drone configuration(" + configuration.getConfigurationName() + ") for "
+ configuration.getClass().getName() + " from Arquillian Descriptor", e);
}
}
/**
* A helper converting method.
*
* Converts string to a class of given type
*
* @param
* Type of returned value
* @param clazz
* Type of desired value
* @param value
* String value to be converted
*
* @return Value converted to a appropriate type
*/
static Object convert(Class> clazz, String value) {
for (ValueMapper> mapper : VALUE_MAPPERS) {
if (mapper.handles(clazz)) {
return mapper.transform(value);
}
}
throw new IllegalArgumentException("Unable to convert value " + value + "to a class: " + clazz.getName());
}
}