/* * 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.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Collection; 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.drone.spi.DroneConfiguration; import org.jboss.arquillian.impl.configuration.api.ArquillianDescriptor; import org.jboss.arquillian.impl.configuration.api.ExtensionDef; /** * Utility which maps Arquillian Descriptor and System Properties to a * configuration. * * @author <a href="kpiwko@redhat.com>Karel Piwko</a> * @see DroneConfiguration */ public class ConfigurationMapper { private static final Logger log = Logger.getLogger(ConfigurationMapper.class.getName()); private Map<String, String> nameValuePairs; private ConfigurationMapper() { this(Collections.<String, String> emptyMap()); } private ConfigurationMapper(Map<String, String> nameValuePairs) { this.nameValuePairs = nameValuePairs; } /** * 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) { String descriptorQualifier = configuration.getConfigurationName(); String qualifierName = qualifier.getSimpleName().toLowerCase(); ConfigurationMapper mapper = new ConfigurationMapper(); mapper.setNameValuePairs(descriptor, descriptorQualifier, qualifierName); return mapper.mapFromArquillianDescriptor(configuration); } /** * Maps a configuration using System Properties * * @param <T> Type of the configuration * @param configuration Configuration object * @param qualifier Qualifier annotation * @return Configured configuration */ public static <T extends DroneConfiguration<T>> T fromSystemConfiguration(T configuration, Class<? extends Annotation> qualifier) { String descriptorQualifier = configuration.getConfigurationName(); String qualifierName = qualifier.getSimpleName().toLowerCase(); ConfigurationMapper mapper = new ConfigurationMapper(); return mapper.mapFromSystemProperties(configuration, descriptorQualifier, qualifierName); } /** * Maps configuration values from Arquillian Descriptor * * @param <T> A type of configuration * @param configuration Configuration object * @return Configured configuration of given type */ private <T extends DroneConfiguration<T>> T mapFromArquillianDescriptor(T configuration) { List<Field> fields = SecurityActions.getAccessableFields(configuration.getClass()); for (Field f : fields) { if (nameValuePairs.containsKey(f.getName())) { try { f.set(configuration, convert(box(f.getType()), nameValuePairs.get(f.getName()))); } catch (Exception e) { throw new RuntimeException("Could not map Drone configuration(" + configuration.getConfigurationName() + ") for " + configuration.getClass().getName() + " from Arquillan Descriptor", e); } } } return configuration; } /** * Maps configuration values from System properties * * @param <T> A type of configuration * @param configuration Configuration object * @param descriptorQualifier A qualifier used for extension configuration in * the descriptor * @param qualifierName Name of the qualifier passed * @return Configured configuration of given type */ private <T extends DroneConfiguration<T>> T mapFromSystemProperties(T configuration, String descriptorQualifier, String qualifierName) { List<Field> fields = SecurityActions.getAccessableFields(configuration.getClass()); String fullQualifiedPrefix = new StringBuilder("arquillian.").append(descriptorQualifier).append("-").append(qualifierName).append(".").toString(); // get fields with qualifier included Map<Field, String> fieldValuePairs = getFieldValuePairs(fields, fullQualifiedPrefix); // get fields without qualifier included if (fieldValuePairs.isEmpty()) { String prefix = new StringBuilder("arquillian.").append(descriptorQualifier).append(".").toString(); fieldValuePairs = getFieldValuePairs(fields, prefix); } for (Map.Entry<Field, String> entry : fieldValuePairs.entrySet()) { try { entry.getKey().set(configuration, convert(box(entry.getKey().getType()), entry.getValue())); } catch (Exception e) { throw new RuntimeException("Could not map Drone configuration(" + configuration.getConfigurationName() + ") for " + configuration.getClass().getName() + " from System properties", e); } } return configuration; } /** * Maps fields to values using System configuration properties * * @param fields Fields to be mapped * @param prefix System property prefix * @return Mapped values */ private Map<Field, String> getFieldValuePairs(Collection<Field> fields, String prefix) { Map<Field, String> fieldValuePairs = new HashMap<Field, String>(); for (Field f : fields) { String fieldName = keyTransform(new StringBuilder(prefix).append(f.getName())); String value = SecurityActions.getProperty(fieldName); if (value != null) { fieldValuePairs.put(f, value); } } return fieldValuePairs; } /** * Parses Arquillian Descriptor into property name - value pairs * * @param descriptor An Arquillian Descriptor * @param descriptorQualifier A qualifier used for extension configuration in * the descriptor * @param qualifierName Name of the qualifier passed */ private void setNameValuePairs(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())) { this.nameValuePairs = extension.getExtensionProperties(); if (log.isLoggable(Level.FINE)) { log.fine("Using <extension qualifier=\"" + extension.getExtensionName() + "\"> for Drone Configuration"); } return; } else if (descriptorQualifier.equals(extension.getExtensionName())) { match = extension; } } // found generic only if (match != null) { this.nameValuePairs = match.getExtensionProperties(); if (log.isLoggable(Level.FINE)) { log.fine("Using <extension qualifier=\"" + match.getExtensionName() + "\"> for Drone Configuration"); } return; } } /** * Maps a field name to a property. * * Replaces camel case with a dot ('.') and lower case character, replaces * other non digit and non letter characters with a dot ('). * * @param fieldName The name of field * @return Corresponding property name */ private String keyTransform(String fieldName) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < fieldName.length(); i++) { char c = fieldName.charAt(i); if (Character.isUpperCase(c)) { sb.append('.').append(Character.toLowerCase(c)); } else if (!Character.isLetterOrDigit(c)) { sb.append('.'); } else { sb.append(c); } } return sb.toString(); } private String keyTransform(StringBuilder fieldName) { return keyTransform(fieldName.toString()); } /** * A helper boxing method. Returns boxed class for a primitive class * * @param primitive A primitive class * @return Boxed class if class was primitive, unchanged class in other cases */ private Class<?> box(Class<?> primitive) { if (!primitive.isPrimitive()) { return primitive; } if (int.class.equals(primitive)) { return Integer.class; } else if (long.class.equals(primitive)) { return Long.class; } else if (float.class.equals(primitive)) { return Float.class; } else if (double.class.equals(primitive)) { return Double.class; } else if (short.class.equals(primitive)) { return Short.class; } else if (boolean.class.equals(primitive)) { return Boolean.class; } else if (char.class.equals(primitive)) { return Character.class; } else if (byte.class.equals(primitive)) { return Byte.class; } throw new IllegalArgumentException("Unknown primitive type " + primitive); } /** * 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 */ private <T> T convert(Class<T> clazz, String value) { if (String.class.equals(clazz)) { return clazz.cast(value); } else if (Integer.class.equals(clazz)) { return clazz.cast(Integer.valueOf(value)); } else if (Double.class.equals(clazz)) { return clazz.cast(Double.valueOf(value)); } else if (Long.class.equals(clazz)) { return clazz.cast(Long.valueOf(value)); } else if (Boolean.class.equals(clazz)) { return clazz.cast(Boolean.valueOf(value)); } else if (URL.class.equals(clazz)) { try { return clazz.cast(new URI(value).toURL()); } catch (MalformedURLException e) { throw new IllegalArgumentException("Unable to convert value " + value + " to URL", e); } catch (URISyntaxException e) { throw new IllegalArgumentException("Unable to convert value " + value + " to URL", e); } } else if (URI.class.equals(clazz)) { try { return clazz.cast(new URI(value)); } catch (URISyntaxException e) { throw new IllegalArgumentException("Unable to convert value " + value + " to URL", e); } } throw new IllegalArgumentException("Unable to convert value " + value + "to a class: " + clazz.getName()); } }