Search in sources :

Example 46 with ObjectRecipe

use of org.apache.xbean.recipe.ObjectRecipe in project component-runtime by Talend.

the class ReflectionService method createObject.

private Object createObject(final ClassLoader loader, final Function<Supplier<Object>, Object> contextualSupplier, final Class clazz, final String[] args, final String name, final Map<String, Object> config) {
    if (PropertyEditors.canConvert(clazz) && config.size() == 1) {
        // direct conversion using the configured
        // converter/editor
        final Object configValue = config.values().iterator().next();
        if (String.class.isInstance(configValue)) {
            return PropertyEditors.getValue(clazz, String.class.cast(configValue));
        }
    }
    final String prefix = name + ".";
    final ObjectRecipe recipe = new ObjectRecipe(clazz);
    recipe.allow(org.apache.xbean.recipe.Option.FIELD_INJECTION);
    recipe.allow(org.apache.xbean.recipe.Option.PRIVATE_PROPERTIES);
    recipe.allow(org.apache.xbean.recipe.Option.CASE_INSENSITIVE_PROPERTIES);
    recipe.allow(org.apache.xbean.recipe.Option.IGNORE_MISSING_PROPERTIES);
    // allows to access not matched properties
    recipe.setProperty("rawProperties", new UnsetPropertiesRecipe());
    // directly
    ofNullable(args).ifPresent(recipe::setConstructorArgNames);
    final Map<String, Object> specificMapping = config.entrySet().stream().filter(e -> e.getKey().startsWith(prefix)).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
    // extract map configuration
    final Map<String, Object> mapEntries = specificMapping.entrySet().stream().filter(e -> {
        final String key = e.getKey();
        final int idxStart = key.indexOf('[', prefix.length());
        return idxStart > 0 && ((idxStart > ".key".length() && key.substring(idxStart - ".key".length(), idxStart).equals(".key")) || (idxStart > ".value".length() && key.substring(idxStart - ".value".length(), idxStart).equals(".value")));
    }).sorted(this::sortIndexEntry).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
    mapEntries.keySet().forEach(specificMapping::remove);
    final Map<String, Object> preparedMaps = new HashMap<>();
    for (final Map.Entry<String, Object> entry : mapEntries.entrySet()) {
        final String key = entry.getKey();
        final int idxStart = key.indexOf('[', prefix.length());
        String enclosingName = key.substring(prefix.length(), idxStart);
        if (enclosingName.endsWith(".key")) {
            enclosingName = enclosingName.substring(0, enclosingName.length() - ".key".length());
        } else if (enclosingName.endsWith(".value")) {
            enclosingName = enclosingName.substring(0, enclosingName.length() - ".value".length());
        } else {
            throw new IllegalArgumentException("'" + key + "' is not supported, it is considered as a map binding");
        }
        if (preparedMaps.containsKey(enclosingName)) {
            continue;
        }
        final Type genericType = findField(enclosingName.substring(enclosingName.indexOf('.') + 1), clazz).getGenericType();
        if (!ParameterizedType.class.isInstance(genericType)) {
            throw new IllegalArgumentException(clazz + "#" + enclosingName + " should be a generic map and not a " + genericType);
        }
        final ParameterizedType pt = ParameterizedType.class.cast(genericType);
        if (pt.getActualTypeArguments().length != 2 || !Class.class.isInstance(pt.getActualTypeArguments()[0]) || !Class.class.isInstance(pt.getActualTypeArguments()[1])) {
            throw new IllegalArgumentException(clazz + "#" + enclosingName + " should be a generic map with a key and value class type (" + pt + ")");
        }
        final Class<?> keyType = Class.class.cast(pt.getActualTypeArguments()[0]);
        final Class<?> valueType = Class.class.cast(pt.getActualTypeArguments()[1]);
        preparedMaps.put(enclosingName, createMap(prefix + enclosingName, Map.class, createObjectFactory(loader, contextualSupplier, keyType), createObjectFactory(loader, contextualSupplier, valueType), createMapCollector(Class.class.cast(pt.getRawType()), keyType, valueType), new HashMap<>(mapEntries)));
    }
    // extract list configuration
    final Map<String, Object> listEntries = specificMapping.entrySet().stream().filter(e -> {
        final String key = e.getKey();
        final int idxStart = key.indexOf('[', prefix.length());
        final int idxEnd = key.indexOf(']', prefix.length());
        final int sep = key.indexOf('.', prefix.length() + 1);
        return idxStart > 0 && key.endsWith("]") && (sep > idxEnd || sep < 0);
    }).sorted(this::sortIndexEntry).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
    listEntries.keySet().forEach(specificMapping::remove);
    final Map<String, Object> preparedLists = new HashMap<>();
    for (final Map.Entry<String, Object> entry : listEntries.entrySet()) {
        final String key = entry.getKey();
        final int idxStart = key.indexOf('[', prefix.length());
        final String enclosingName = key.substring(prefix.length(), idxStart);
        if (preparedLists.containsKey(enclosingName)) {
            continue;
        }
        final Type genericType = findField(enclosingName, clazz).getGenericType();
        if (Class.class.isInstance(genericType)) {
            final Class<?> arrayClass = Class.class.cast(genericType);
            if (arrayClass.isArray()) {
                // we could use Array.newInstance but for now use the list, shouldn't impact
                // much the perf
                final Collection<?> list = Collection.class.cast(createList(loader, contextualSupplier, prefix + enclosingName, List.class, arrayClass.getComponentType(), toList(), createObjectFactory(loader, contextualSupplier, arrayClass.getComponentType()), new HashMap<>(listEntries)));
                // we need that conversion to ensure the type matches
                final Object array = Array.newInstance(arrayClass.getComponentType(), list.size());
                int idx = 0;
                for (final Object o : list) {
                    Array.set(array, idx++, o);
                }
                preparedLists.put(enclosingName, array);
                continue;
            }
        // else let it fail with the "collection" error
        }
        if (!ParameterizedType.class.isInstance(genericType)) {
            throw new IllegalArgumentException(clazz + "#" + enclosingName + " should be a generic collection and not a " + genericType);
        }
        final ParameterizedType pt = ParameterizedType.class.cast(genericType);
        if (pt.getActualTypeArguments().length != 1 || !Class.class.isInstance(pt.getActualTypeArguments()[0])) {
            throw new IllegalArgumentException(clazz + "#" + enclosingName + " should use concrete class items and not a " + pt.getActualTypeArguments()[0]);
        }
        final Type itemType = pt.getActualTypeArguments()[0];
        preparedLists.put(enclosingName, createList(loader, contextualSupplier, prefix + enclosingName, Class.class.cast(pt.getRawType()), Class.class.cast(itemType), toList(), createObjectFactory(loader, contextualSupplier, itemType), new HashMap<>(listEntries)));
    }
    // extract nested Object configurations
    final Map<String, Object> objectEntries = specificMapping.entrySet().stream().filter(e -> {
        final String key = e.getKey();
        return key.indexOf('.', prefix.length() + 1) > 0;
    }).sorted((o1, o2) -> {
        final String key1 = o1.getKey();
        final String key2 = o2.getKey();
        if (key1.equals(key2)) {
            return 0;
        }
        final String nestedName1 = key1.substring(prefix.length(), key1.indexOf('.', prefix.length() + 1));
        final String nestedName2 = key2.substring(prefix.length(), key2.indexOf('.', prefix.length() + 1));
        final int idxStart1 = nestedName1.indexOf('[');
        final int idxStart2 = nestedName2.indexOf('[');
        if (idxStart1 > 0 && idxStart2 > 0 && nestedName1.substring(0, idxStart1).equals(nestedName2.substring(0, idxStart2))) {
            final int idx1 = Integer.parseInt(nestedName1.substring(idxStart1 + 1, nestedName1.length() - 1));
            final int idx2 = Integer.parseInt(nestedName2.substring(idxStart2 + 1, nestedName2.length() - 1));
            return idx1 - idx2;
        }
        return key1.compareTo(key2);
    }).collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (o, o2) -> {
        throw new IllegalArgumentException("Can't merge " + o + " and " + o2);
    }, LinkedHashMap::new));
    objectEntries.keySet().forEach(specificMapping::remove);
    final Map<String, Object> preparedObjects = new HashMap<>();
    for (final Map.Entry<String, Object> entry : objectEntries.entrySet()) {
        final String nestedName = entry.getKey().substring(prefix.length(), entry.getKey().indexOf('.', prefix.length() + 1));
        if (nestedName.endsWith("]")) {
            // complex lists
            final int idxStart = nestedName.indexOf('[');
            if (idxStart > 0) {
                final String listName = nestedName.substring(0, idxStart);
                final Field field = findField(listName, clazz);
                if (ParameterizedType.class.isInstance(field.getGenericType())) {
                    final ParameterizedType pt = ParameterizedType.class.cast(field.getGenericType());
                    if (Class.class.isInstance(pt.getRawType())) {
                        final Class<?> rawType = Class.class.cast(pt.getRawType());
                        if (Set.class.isAssignableFrom(rawType)) {
                            addListElement(loader, contextualSupplier, config, prefix, preparedObjects, nestedName, listName, pt, () -> new HashSet<>(2));
                        } else if (Collection.class.isAssignableFrom(rawType)) {
                            addListElement(loader, contextualSupplier, config, prefix, preparedObjects, nestedName, listName, pt, () -> new ArrayList<>(2));
                        } else {
                            throw new IllegalArgumentException("unsupported configuration type: " + pt);
                        }
                        continue;
                    } else {
                        throw new IllegalArgumentException("unsupported configuration type: " + pt);
                    }
                } else {
                    throw new IllegalArgumentException("unsupported configuration type: " + field.getType());
                }
            } else {
                throw new IllegalArgumentException("unsupported configuration type: " + nestedName);
            }
        }
        final Field field = findField(nestedName, clazz);
        preparedObjects.put(nestedName, createObject(loader, contextualSupplier, field.getType(), findArgsName(field.getType()), prefix + nestedName, config));
    }
    // other entries can be directly set
    final Map<String, Object> normalizedConfig = specificMapping.entrySet().stream().filter(e -> e.getKey().startsWith(prefix) && e.getKey().substring(prefix.length()).indexOf('.') < 0).collect(toMap(e -> {
        final String specificConfig = e.getKey().substring(prefix.length());
        final int index = specificConfig.indexOf('[');
        if (index > 0) {
            final int end = specificConfig.indexOf(']', index);
            if (end > index) {
                // > 0 would work too
                // here we need to normalize it to let xbean understand it
                String leadingString = specificConfig.substring(0, index);
                if (leadingString.endsWith(".key") || leadingString.endsWith(".value")) {
                    // map
                    leadingString = leadingString.substring(0, leadingString.lastIndexOf('.'));
                }
                return leadingString + specificConfig.substring(end + 1);
            }
        }
        return specificConfig;
    }, Map.Entry::getValue));
    // now bind it all to the recipe and builder the instance
    preparedMaps.forEach(recipe::setProperty);
    preparedLists.forEach(recipe::setProperty);
    preparedObjects.forEach(recipe::setProperty);
    normalizedConfig.forEach(recipe::setProperty);
    return recipe.create(loader);
}
Also used : Array(java.lang.reflect.Array) UnsetPropertiesRecipe(org.apache.xbean.recipe.UnsetPropertiesRecipe) ConstructorProperties(java.beans.ConstructorProperties) BiFunction(java.util.function.BiFunction) RequiredArgsConstructor(lombok.RequiredArgsConstructor) HashMap(java.util.HashMap) Function(java.util.function.Function) Supplier(java.util.function.Supplier) ArrayList(java.util.ArrayList) ConcurrentMap(java.util.concurrent.ConcurrentMap) HashSet(java.util.HashSet) LinkedHashMap(java.util.LinkedHashMap) Collectors.toMap(java.util.stream.Collectors.toMap) Map(java.util.Map) Executable(java.lang.reflect.Executable) Collector(java.util.stream.Collector) Collectors.toSet(java.util.stream.Collectors.toSet) Optional.ofNullable(java.util.Optional.ofNullable) Collectors.toConcurrentMap(java.util.stream.Collectors.toConcurrentMap) Collection(java.util.Collection) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) Set(java.util.Set) PropertyEditors(org.apache.xbean.propertyeditor.PropertyEditors) Field(java.lang.reflect.Field) ObjectRecipe(org.apache.xbean.recipe.ObjectRecipe) Collectors.toList(java.util.stream.Collectors.toList) List(java.util.List) Stream(java.util.stream.Stream) ParameterizedType(java.lang.reflect.ParameterizedType) Type(java.lang.reflect.Type) Comparator(java.util.Comparator) Collections(java.util.Collections) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) ArrayList(java.util.ArrayList) UnsetPropertiesRecipe(org.apache.xbean.recipe.UnsetPropertiesRecipe) ParameterizedType(java.lang.reflect.ParameterizedType) Field(java.lang.reflect.Field) ParameterizedType(java.lang.reflect.ParameterizedType) Type(java.lang.reflect.Type) ObjectRecipe(org.apache.xbean.recipe.ObjectRecipe) Collection(java.util.Collection) ArrayList(java.util.ArrayList) Collectors.toList(java.util.stream.Collectors.toList) List(java.util.List) HashMap(java.util.HashMap) ConcurrentMap(java.util.concurrent.ConcurrentMap) LinkedHashMap(java.util.LinkedHashMap) Collectors.toMap(java.util.stream.Collectors.toMap) Map(java.util.Map) Collectors.toConcurrentMap(java.util.stream.Collectors.toConcurrentMap) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap)

Aggregations

ObjectRecipe (org.apache.xbean.recipe.ObjectRecipe)46 Map (java.util.Map)20 HashMap (java.util.HashMap)16 Properties (java.util.Properties)13 IOException (java.io.IOException)12 NamingException (javax.naming.NamingException)9 OpenEJBException (org.apache.openejb.OpenEJBException)9 InvocationTargetException (java.lang.reflect.InvocationTargetException)7 Field (java.lang.reflect.Field)6 Set (java.util.Set)6 File (java.io.File)5 ArrayList (java.util.ArrayList)5 TreeMap (java.util.TreeMap)5 Collectors.toList (java.util.stream.Collectors.toList)5 Stream (java.util.stream.Stream)5 MalformedURLException (java.net.MalformedURLException)4 Objects (java.util.Objects)4 SuperProperties (org.apache.openejb.util.SuperProperties)4 URISyntaxException (java.net.URISyntaxException)3 List (java.util.List)3