/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.aries.blueprint.container; import java.io.ByteArrayInputStream; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Hashtable; import java.util.Dictionary; import java.util.Enumeration; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import java.math.BigInteger; import java.math.BigDecimal; import org.apache.aries.blueprint.ExtendedBlueprintContainer; import org.apache.aries.blueprint.di.CollectionRecipe; import org.apache.aries.blueprint.di.MapRecipe; import org.apache.aries.blueprint.utils.ReflectionUtils; import static org.apache.aries.blueprint.utils.ReflectionUtils.getRealCause; import org.osgi.service.blueprint.container.ReifiedType; import org.osgi.service.blueprint.container.Converter; /** * Implementation of the Converter. * * This object contains all the registered Converters which can be registered * by using {@link #registerConverter(Converter)} * and unregistered using {@link #unregisterConverter(Converter)}. * * Each {@link org.osgi.service.blueprint.container.BlueprintContainer} has its own AggregateConverter * used to register converters defined by the related blueprint bundle. * * @version $Rev: 990789 $, $Date: 2010-08-30 13:42:03 +0100 (Mon, 30 Aug 2010) $ */ public class AggregateConverter implements Converter { /** * Objects implementing this interface will bypass the default conversion rules * and be called directly to transform into the expected type. */ public static interface Convertible { Object convert(ReifiedType type) throws Exception; } private static class ConversionResult { public final Converter converter; public final Object value; public ConversionResult(Converter converter, Object value) { this.converter = converter; this.value = value; } } private ExtendedBlueprintContainer blueprintContainer; private List<Converter> converters = new ArrayList<Converter>(); public AggregateConverter(ExtendedBlueprintContainer blueprintContainer) { this.blueprintContainer = blueprintContainer; } public void registerConverter(Converter converter) { converters.add(converter); } public void unregisterConverter(Converter converter) { converters.remove(converter); } public boolean canConvert(final Object fromValue, final ReifiedType toType) { if (fromValue == null) { return true; } if (isAssignable(fromValue, toType)) { return true; } boolean canConvert = false; AccessControlContext acc = blueprintContainer.getAccessControlContext(); if (acc == null) { canConvert = canConvertWithConverters(fromValue, toType); } else { canConvert = AccessController.doPrivileged(new PrivilegedAction<Boolean>() { public Boolean run() { return canConvertWithConverters(fromValue, toType); } }, acc); } if (canConvert) { return true; } // TODO implement better logic ?! try { convert(fromValue, toType); return true; } catch (Exception e) { return false; } } public Object convert(final Object fromValue, final ReifiedType type) throws Exception { // Discard null values if (fromValue == null) { return null; } // First convert service proxies if (fromValue instanceof Convertible) { return ((Convertible) fromValue).convert(type); } // If the object is an instance of the type, just return it if (isAssignable(fromValue, type)) { return fromValue; } ConversionResult result = null; AccessControlContext acc = blueprintContainer.getAccessControlContext(); if (acc == null) { result = convertWithConverters(fromValue, type); } else { result = AccessController.doPrivileged(new PrivilegedExceptionAction<ConversionResult>() { public ConversionResult run() throws Exception { return convertWithConverters(fromValue, type); } }, acc); } if (result == null) { if (fromValue instanceof Number && Number.class.isAssignableFrom(unwrap(toClass(type)))) { return convertToNumber((Number) fromValue, toClass(type)); } else if (fromValue instanceof String) { return convertFromString((String) fromValue, toClass(type), blueprintContainer); } else if (toClass(type).isArray() && (fromValue instanceof Collection || fromValue.getClass().isArray())) { return convertToArray(fromValue, type); } else if (Map.class.isAssignableFrom(toClass(type)) && (fromValue instanceof Map || fromValue instanceof Dictionary)) { return convertToMap(fromValue, type); } else if (Dictionary.class.isAssignableFrom(toClass(type)) && (fromValue instanceof Map || fromValue instanceof Dictionary)) { return convertToDictionary(fromValue, type); } else if (Collection.class.isAssignableFrom(toClass(type)) && (fromValue instanceof Collection || fromValue.getClass().isArray())) { return convertToCollection(fromValue, type); } else { throw new Exception("Unable to convert value " + fromValue + " to type " + type); } } return result.value; } private Converter selectMatchingConverter(Object source, ReifiedType type) { for (Converter converter : converters) { if (converter.canConvert(source, type)) { return converter; } } return null; } private boolean canConvertWithConverters(Object source, ReifiedType type) { return selectMatchingConverter(source,type) != null; } private ConversionResult convertWithConverters(Object source, ReifiedType type) throws Exception { Converter converter = selectMatchingConverter(source,type); if (converter == null) return null; Object value = converter.convert(source, type); return new ConversionResult(converter,value); } public Object convertToNumber(Number value, Class toType) throws Exception { toType = unwrap(toType); if (AtomicInteger.class == toType) { return new AtomicInteger((Integer) convertToNumber(value, Integer.class)); } else if (AtomicLong.class == toType) { return new AtomicLong((Long) convertToNumber(value, Long.class)); } else if (Integer.class == toType) { return value.intValue(); } else if (Short.class == toType) { return value.shortValue(); } else if (Long.class == toType) { return value.longValue(); } else if (Float.class == toType) { return value.floatValue(); } else if (Double.class == toType) { return value.doubleValue(); } else if (Byte.class == toType) { return value.byteValue(); } else if (BigInteger.class == toType) { return new BigInteger(value.toString()); } else if (BigDecimal.class == toType) { return new BigDecimal(value.toString()); } else { throw new Exception("Unable to convert number " + value + " to " + toType); } } public Object convertFromString(String value, Class toType, Object loader) throws Exception { toType = unwrap(toType); if (ReifiedType.class == toType) { try { return GenericType.parse(value, loader); } catch (ClassNotFoundException e) { throw new Exception("Unable to convert", e); } } else if (Class.class == toType) { try { return GenericType.parse(value, loader).getRawClass(); } catch (ClassNotFoundException e) { throw new Exception("Unable to convert", e); } } else if (Locale.class == toType) { String[] tokens = value.split("_"); if (tokens.length == 1) { return new Locale(tokens[0]); } else if (tokens.length == 2) { return new Locale(tokens[0], tokens[1]); } else if (tokens.length == 3) { return new Locale(tokens[0], tokens[1], tokens[2]); } else { throw new Exception("Invalid locale string:" + value); } } else if (Pattern.class == toType) { return Pattern.compile(value); } else if (Properties.class == toType) { Properties props = new Properties(); ByteArrayInputStream in = new ByteArrayInputStream(value.getBytes("UTF8")); props.load(in); return props; } else if (Boolean.class == toType) { if ("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value)) { return Boolean.TRUE; } else if ("no".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value) || "off".equalsIgnoreCase(value)) { return Boolean.FALSE; } else { throw new RuntimeException("Invalid boolean value: " + value); } } else if (Integer.class == toType) { return Integer.valueOf(value); } else if (Short.class == toType) { return Short.valueOf(value); } else if (Long.class == toType) { return Long.valueOf(value); } else if (Float.class == toType) { return Float.valueOf(value); } else if (Double.class == toType) { return Double.valueOf(value); } else if (Character.class == toType) { if (value.length() == 6 && value.startsWith("\\u")) { int code = Integer.parseInt(value.substring(2), 16); return (char)code; } else if (value.length() == 1) { return value.charAt(0); } else { throw new Exception("Invalid value for character type: " + value); } } else if (Byte.class == toType) { return Byte.valueOf(value); } else if (Enum.class.isAssignableFrom(toType)) { return Enum.valueOf((Class<Enum>) toType, value); } else { return createObject(value, toType); } } private Object createObject(String value, Class type) throws Exception { if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { throw new Exception("Unable to convert value " + value + " to type " + type + ". Type " + type + " is an interface or an abstract class"); } Constructor constructor = null; try { constructor = type.getConstructor(String.class); } catch (NoSuchMethodException e) { throw new RuntimeException("Unable to convert to " + type); } try { return ReflectionUtils.newInstance(blueprintContainer.getAccessControlContext(), constructor, value); } catch (Exception e) { throw new Exception("Unable to convert ", getRealCause(e)); } } private Object convertToCollection(Object obj, ReifiedType type) throws Exception { ReifiedType valueType = type.getActualTypeArgument(0); Collection newCol = (Collection) ReflectionUtils.newInstance(blueprintContainer.getAccessControlContext(), CollectionRecipe.getCollection(toClass(type))); if (obj.getClass().isArray()) { for (int i = 0; i < Array.getLength(obj); i++) { try { newCol.add(convert(Array.get(obj, i), valueType)); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting array element)", t); } } } else { for (Object item : (Collection) obj) { try { newCol.add(convert(item, valueType)); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting collection entry)", t); } } } return newCol; } private Object convertToDictionary(Object obj, ReifiedType type) throws Exception { ReifiedType keyType = type.getActualTypeArgument(0); ReifiedType valueType = type.getActualTypeArgument(1); Dictionary newDic = new Hashtable(); if (obj instanceof Dictionary) { Dictionary dic = (Dictionary) obj; for (Enumeration keyEnum = dic.keys(); keyEnum.hasMoreElements();) { Object key = keyEnum.nextElement(); try { newDic.put(convert(key, keyType), convert(dic.get(key), valueType)); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t); } } } else { for (Map.Entry e : ((Map<Object,Object>) obj).entrySet()) { try { newDic.put(convert(e.getKey(), keyType), convert(e.getValue(), valueType)); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t); } } } return newDic; } private Object convertToMap(Object obj, ReifiedType type) throws Exception { ReifiedType keyType = type.getActualTypeArgument(0); ReifiedType valueType = type.getActualTypeArgument(1); Map newMap = (Map) ReflectionUtils.newInstance(blueprintContainer.getAccessControlContext(), MapRecipe.getMap(toClass(type))); if (obj instanceof Dictionary) { Dictionary dic = (Dictionary) obj; for (Enumeration keyEnum = dic.keys(); keyEnum.hasMoreElements();) { Object key = keyEnum.nextElement(); try { newMap.put(convert(key, keyType), convert(dic.get(key), valueType)); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t); } } } else { for (Map.Entry e : ((Map<Object,Object>) obj).entrySet()) { try { newMap.put(convert(e.getKey(), keyType), convert(e.getValue(), valueType)); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t); } } } return newMap; } private Object convertToArray(Object obj, ReifiedType type) throws Exception { if (obj instanceof Collection) { obj = ((Collection) obj).toArray(); } if (!obj.getClass().isArray()) { throw new Exception("Unable to convert from " + obj + " to " + type); } ReifiedType componentType; if (type.size() > 0) { componentType = type.getActualTypeArgument(0); } else { componentType = new GenericType(type.getRawClass().getComponentType()); } Object array = Array.newInstance(toClass(componentType), Array.getLength(obj)); for (int i = 0; i < Array.getLength(obj); i++) { try { Array.set(array, i, convert(Array.get(obj, i), componentType)); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting array element)", t); } } return array; } public static boolean isAssignable(Object source, ReifiedType target) { return source == null || (target.size() == 0 && unwrap(target.getRawClass()).isAssignableFrom(unwrap(source.getClass()))); } private static Class unwrap(Class c) { Class u = primitives.get(c); return u != null ? u : c; } private static final Map<Class, Class> primitives; static { primitives = new HashMap<Class, Class>(); primitives.put(byte.class, Byte.class); primitives.put(short.class, Short.class); primitives.put(char.class, Character.class); primitives.put(int.class, Integer.class); primitives.put(long.class, Long.class); primitives.put(float.class, Float.class); primitives.put(double.class, Double.class); primitives.put(boolean.class, Boolean.class); } public Object convert(Object source, Type target) throws Exception { return convert( source, new GenericType(target)); } private Class toClass(ReifiedType type) { return type.getRawClass(); } }