Project: arquillian-extension-drone
Code Examples
/*
 * 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.LegacyPropertyToCapabilityMapper; 
import org.jboss.arquillian.drone.configuration.mapping.BooleanValueMapper; 
import org.jboss.arquillian.drone.configuration.mapping.DoubleValueMapper; 
import org.jboss.arquillian.drone.configuration.mapping.IntegerValueMapper; 
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 and System Properties 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<String,String>} fields. Properties using their name as a key. 
 * 
 * @author <a href="[email protected]>Karel Piwko</a> 
 * @see DroneConfiguration 
 */
 
public class ConfigurationMapper { 
    private static final Logger log = Logger.getLogger(ConfigurationMapper.class.getName()); 
 
    // FIXME this should not be a static helper class but a proper observer on ArquillianDescriptor 
    private ConfigurationMapper() { 
        throw new InstantiationError(); 
    } 
 
    // FIXME this should be in SPI with a proper event model 
    public static final List<ValueMapper<?>> VALUE_MAPPERS; 
    static { 
        VALUE_MAPPERS = new ArrayList<ValueMapper<?>>(); 
        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); 
    } 
 
    /**
     * Maps a configuration using Arquillian Descriptor file 
     * 
     * @param <T> Type of the configuration 
     * @param descriptor Arquillian Descriptor 
     * @param configuration Configuration object 
     * @param qualifier Qualifier annotation 
     * @return Configured configuration 
     */
 
    public static <T extends DroneConfiguration<T>> 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<String, String> nameValuePairs = loadNameValuePairs(descriptor, descriptorQualifier, qualifierName); 
 
        return mapFromNameValuePairs(configuration, nameValuePairs); 
    } 
 
    /**
     * Maps a configuration using System Properties. 
     * 
     * This method is <b>deprecated</b>. Use Arquillian Core property format instead. 
     * 
     * @param <T> Type of the configuration 
     * @param configuration Configuration object 
     * @param qualifier Qualifier annotation 
     * @return Configured configuration 
     */
 
    @Deprecated 
    public static <T extends DroneConfiguration<T>> T fromSystemConfiguration(T configuration, 
            Class<? extends Annotation> qualifier) { 
        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<String, String> nameValuePairs = loadNameValuePairs(descriptorQualifier, qualifierName); 
 
        return mapFromNameValuePairs(configuration, nameValuePairs); 
    } 
 
    /**
     * Maps configuration values from Arquillian Descriptor 
     * 
     * @param <T> A type of configuration 
     * @param configuration Configuration object 
     * @return Configured configuration of given type 
     */
 
    // @SuppressWarnings("unchecked") 
    static <T extends DroneConfiguration<T>> T mapFromNameValuePairs(T configuration, Map<String, String> nameValuePairs) { 
        Map<String, Field> fields = SecurityActions.getAccessableFields(configuration.getClass()); 
 
        // extract all Map<String,String> in the configuration and initialize them 
        List<Field> maps = SecurityActions.getMapFields(configuration.getClass(), String.class, String.class); 
        for (Field mapField : maps) { 
            try { 
                // get or create a map 
                @SuppressWarnings("unchecked"
                Map<String, String> map = (Map<String, String>) mapField.get(configuration); 
                if (map == null) { 
                    map = new HashMap<String, String>(); 
                } 
                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<String, String> nameValue : nameValuePairs.entrySet()) { 
            String name = nameValue.getKey(); 
 
            // 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 
            else if (fields.containsKey(keyTransformReverse(name))) { 
                // 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<String, String> 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<String, String> nameValuePairs = extension.getExtensionProperties(); 
                if (log.isLoggable(Level.FINE)) { 
                    log.fine("Using <extension qualifier=\"" + extension.getExtensionName() + "\"> for Drone Configuration"); 
                } 
                return nameValuePairs; 
            } else if (descriptorQualifier.equals(extension.getExtensionName())) { 
                match = extension; 
            } 
        } 
 
        // found generic only 
        if (match != null) { 
            Map<String, String> nameValuePairs = match.getExtensionProperties(); 
            if (log.isLoggable(Level.FINE)) { 
                log.fine("Using <extension qualifier=\"" + match.getExtensionName() + "\"> for Drone Configuration"); 
            } 
            return nameValuePairs; 
        } 
 
        return Collections.emptyMap(); 
    } 
 
    /**
     * Parses System properties into property name - value pairs. 
     * 
     * This method is now deprecated and should not be used anymore. Arquillian Core contains possibility to load configuration 
     * from System properties. 
     * 
     * @param descriptorQualifier A qualifier used for extension configuration in the descriptor 
     * @param qualifierName Name of the qualifier passed 
     */
 
    @Deprecated 
    static Map<String, String> loadNameValuePairs(String descriptorQualifier, String qualifierName) { 
        String fullQualifiedPrefix = new StringBuilder("arquillian.").append(descriptorQualifier).append("."
                .append(qualifierName).append(".").toString(); 
 
        String qualifiedPrefix = new StringBuilder("arquillian.").append(descriptorQualifier).append(".").toString(); 
 
        // try to get fully qualified prefix properties first 
        Map<String, String> candidates = SecurityActions.getProperties(fullQualifiedPrefix); 
        if (candidates.isEmpty()) { 
            candidates.putAll(SecurityActions.getProperties(qualifiedPrefix)); 
        } 
 
        // properly rename 
        Map<String, String> nameValuePairs = new HashMap<String, String>(candidates.size()); 
        for (Map.Entry<String, String> entry : candidates.entrySet()) { 
            String name = entry.getKey(); 
 
            // make a nasty warning that this will be removed 
            if (log.isLoggable(Level.WARNING)) { 
                String propertyName = name.contains(fullQualifiedPrefix) ? name.substring(fullQualifiedPrefix.length()) : name 
                        .substring(qualifiedPrefix.length()); 
 
                String newSysPropertyKey = new StringBuilder("arq.extension."
                        .append(name.contains(fullQualifiedPrefix) ? (descriptorQualifier + "-" + qualifierName) 
                                : descriptorQualifier).append(".").append(propertyName).toString(); 
 
                log.log(Level.WARNING, "Old system property format \"{0}\" is deprecated. You should use \"{1}\" instead."
                        new Object[] { name, newSysPropertyKey }); 
            } 
            // trim name 
            name = name.contains(fullQualifiedPrefix) ? name.substring(fullQualifiedPrefix.length()) : name 
                    .substring(qualifiedPrefix.length()); 
            nameValuePairs.put(name, entry.getValue()); 
        } 
 
        return nameValuePairs; 
 
    } 
 
    /**
     * 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 <T extends DroneConfiguration<T>> Field injectField(T configuration, List<Field> maps, Map<String, Field> 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 
            if (LegacyPropertyToCapabilityMapper.isLegacy(fieldName)) { 
                String capabilityName = LegacyPropertyToCapabilityMapper.remapKey(fieldName); 
                String capabilityValue = LegacyPropertyToCapabilityMapper.remapValue(fieldName, value); 
                injectMapProperty(configuration, maps, fields, capabilityName, capabilityValue); 
            } 
 
            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 <T extends DroneConfiguration<T>> void injectMapProperty(T configuration, List<Field> maps, 
            Map<String, Field> fields, String propertyName, String value) { 
 
        try { 
            for (Field mapField : maps) { 
                try { 
                    // put property into a map 
                    @SuppressWarnings("unchecked"
                    Map<String, String> map = (Map<String, String>) mapField.get(configuration); 
                    map.put(propertyName, value); 
                } catch (Exception e) { 
                    throw new RuntimeException("Could not map Drone configuration(" + configuration.getConfigurationName() 
                            + ") for " + configuration.getClass().getName() + " from Arquillian Descriptor", e); 
                } 
            } 
        } 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 <T> 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()); 
    } 
}