dinistiq.Dinistiq.java Source code

Java tutorial

Introduction

Here is the source code for dinistiq.Dinistiq.java

Source

/**
 *
 * Copyright 2013-2015 Martin Goellnitz
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package dinistiq;

import java.beans.Introspector;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The dinistiq main class.
 *
 * By instanciating this class the bean creation and injection process will be started. Subsequent calls to the
 * API methods support retrieval of the singleton beans according to type, annotation, or type and annotation.
 */
public class Dinistiq {

    private static final Logger LOG = LoggerFactory.getLogger(Dinistiq.class);

    private static final String PRODUCT_BASE_PATH = "dinistiq";

    private static final String JAVALANG_PREFIX = "java.lang.";

    private static final String MAP_TYPE = "java.util.Map";

    private static final String LIST_TYPE = "java.util.List";

    private static final String SET_TYPE = "java.util.Set";

    private static final Pattern REPLACEMENT_PATTERN = Pattern.compile("\\$\\{[a-zA-Z0-9_\\.]*\\}");

    private final Map<String, String> environment = new HashMap<>(System.getenv());

    private final List<Object> orderedBeans = new ArrayList<>();

    private final Map<String, Object> beans = new HashMap<>();

    /**
     * Small helper method to keep areas with suppressed warnings small.
     *
     * @param <T>
     * @param bean
     */
    @SuppressWarnings("unchecked")
    private <T extends Object> T convert(Object bean) {
        return (T) bean;
    } // convert()

    /**
     * Find all beans of a given type.
     *
     * @param <T> type to check resulting beans for
     * @param type instance of that type
     * @return Set of beans - may be empty but not null
     */
    public <T extends Object> Set<T> findBeans(Class<T> type) {
        Set<T> result = new HashSet<>();
        for (Object bean : beans.values()) {
            if (type.isAssignableFrom(bean.getClass())) {
                LOG.info("findBeans() adding to result {}:{}", bean, type.getName());
                T b = convert(bean);
                result.add(b);
            } // if
        } // for
        return result;
    } // findBeans()

    /**
     * Find all names of beans of a given type.
     *
     * @param <T> type to check resulting beans for
     * @param type instance of that type
     * @return Set of bean names - may be empty but not null
     */
    public <T extends Object> Set<String> findNames(Class<T> type) {
        Set<String> result = new HashSet<>();
        for (String name : beans.keySet()) {
            Object bean = beans.get(name);
            if (type.isAssignableFrom(bean.getClass())) {
                LOG.info("findNames() adding to result {} :{}", bean, type.getName());
                result.add(name);
            } // if
        } // for
        return result;
    } // findNames()

    /**
     * Find all beans with a given annotation.
     *
     * @param <A> type to check resulting beans for
     * @param type instance of that type
     * @return Set of beans - may be empty but not null
     */
    public <A extends Annotation> Set<Object> findAnnotatedBeans(Class<A> type) {
        Set<Object> result = new HashSet<>();
        for (Object bean : beans.values()) {
            if (bean.getClass().getAnnotation(type) != null) {
                LOG.info("findAnnotatedBeans() adding to result {} :{}", bean, type.getName());
                result.add(bean);
            } // if
        } // for
        return result;
    } // findAnnotatedBeans()

    /**
     * Find exactly one bean of a given type.
     * If there are more beans of that type just one of them is returned. This is fairly random by design.
     *
     * @param <T> type to check resulting bean for
     * @param type instance of that type
     * @return resulting bean or null
     */
    public <T extends Object> T findBean(Class<T> type) {
        Set<T> allBeans = findBeans(type);
        return (allBeans.size() > 0) ? allBeans.iterator().next() : null;
    } // findBean()

    /**
     * Find exactly one beans of a given type and name.
     * Return null if not both conditions are met.
     *
     * @param <T> type to check resulting bean for
     * @param cls instance of that type
     * @param name name the searched bean must have
     * @return resulting bean or null
     */
    public <T extends Object> T findBean(Class<? extends T> cls, String name) {
        T result = null;
        Object bean = beans.get(name);
        if (bean != null) {
            LOG.info("findBean() {} :{}", name, bean.getClass().getName());
            if (cls.isAssignableFrom(bean.getClass())) {
                result = convert(bean);
            } // if
        } // if
        return result;
    } // findBean()

    /**
     * Return the names of all beans in the inistiq scope.
     *
     * @return collection of all bean names
     */
    public Collection<String> getAllBeanNames() {
        return beans.keySet();
    } // getAllBeanNames()

    /**
     * Tries to resolve the value for a given placeholder.
     *
     * @param beanProperties properties of the beans to resolve the value for
     * @param dependencies global dependencies collector to store retrieved reference values as dependecy in
     * @param customer "customer" descriptor for logging who needed the value resolved
     * @param cls target class of the value
     * @param type target type of the value
     * @param name name of the placeholder
     * @return replaced value or original string
     * @throws Exception
     */
    private Object getValue(Properties beanProperties, Map<String, Set<Object>> dependencies, String customer,
            Class<?> cls, Type type, final String name) throws Exception {
        ParameterizedType parameterizedType = (type instanceof ParameterizedType) ? (ParameterizedType) type : null;
        if ((name == null) && Collection.class.isAssignableFrom(cls)) {
            LOG.debug("getValue() collection: {}", type);
            if (parameterizedType != null) {
                Type collectionType = parameterizedType.getActualTypeArguments()[0];
                LOG.debug("getValue() inner type {}", collectionType);
                Collection<? extends Object> resultCollection = findBeans((Class<? extends Object>) collectionType);
                resultCollection = List.class.isAssignableFrom(cls) ? new ArrayList<>(resultCollection)
                        : resultCollection;
                if (dependencies != null) {
                    dependencies.get(customer).addAll(resultCollection);
                } // if
                return resultCollection;
            } // if
        } // if
        Object bean = (name == null) ? findBean(cls)
                : (beanProperties.containsKey(name) ? getReferenceValue(beanProperties.getProperty(name))
                        : beans.get(name));
        if (Provider.class.equals(cls)) {
            final Dinistiq d = this;
            final Class<? extends Object> c = (Class<?>) parameterizedType.getActualTypeArguments()[0];
            LOG.info("getValue() Provider for {} :{}", name, c);
            bean = new Provider<Object>() {

                @Override
                public Object get() {
                    return (name == null) ? d.findBean(c) : d.findBean(c, name);
                }

            };
            String beanName = "" + bean;
            if (dependencies != null) {
                dependencies.put(beanName, new HashSet<>());
            } // if
            beans.put(beanName, bean);
        } // if
        if (cls.isAssignableFrom(bean.getClass())) {
            if ((dependencies != null) && beans.containsValue(bean)) {
                dependencies.get(customer).add(bean);
            } // if
            return bean;
        } // if
        throw new Exception("for " + customer + ": no bean " + (name != null ? name + " :" : "of type ")
                + cls.getSimpleName() + " found.");
    } // getValue()

    /**
     * Obtain a parameter array to call a method with injections or a constructor with injections.
     *
     * @param dependencies map of dependencies for beans - pass null if you don't want to record needed dependencies
     * @param beanName name of the bean
     * @param types types array for the call
     * @param genericTypes generic type array for the call
     * @param annotations annotations of the parameters
     * @return array suitable as parameter for invoke or newInstance calls
     * @throws Exception
     */
    private Object[] getParameters(Properties beanProperties, Map<String, Set<Object>> dependencies,
            String beanName, Class<? extends Object>[] types, Type[] genericTypes, Annotation[][] annotations)
            throws Exception {
        beanProperties = beanProperties == null ? new Properties() : beanProperties;
        Object[] parameters = new Object[types.length];
        for (int i = 0; i < types.length; i++) {
            String name = null;
            for (Annotation a : annotations[i]) {
                if (a instanceof Named) {
                    name = ((Named) a).value();
                } // if
            } // for
            parameters[i] = getValue(beanProperties, dependencies, beanName, types[i], genericTypes[i], name);
        } // for
        return parameters;
    } // getParameters()

    /**
     * Creates an instance of the given type and registeres it with the container.
     *
     * @param dependencies dependencies within the scope
     * @param cls type to create an instance of
     * @param beanName beans name in the scope using the given dependencies
     */
    private <T extends Object> T createInstance(Map<String, Set<Object>> dependencies, Class<T> cls,
            String beanName) throws Exception {
        LOG.info("createInstance({})", cls.getSimpleName());
        Constructor<?> c = null;
        Constructor<?>[] constructors = cls.getDeclaredConstructors();
        LOG.debug("createInstance({}) constructors.length={}", cls.getSimpleName(), constructors.length);
        for (Constructor<?> ctor : constructors) {
            LOG.debug("createInstance({}) {}", cls.getSimpleName(), ctor);
            c = (ctor.getAnnotation(Inject.class) != null) ? ctor : c;
        } // for
        c = (c == null) ? cls.getConstructor() : c;
        // Don't record constructor dependencies - they MUST be already fulfilled
        Object[] parameters = getParameters(null, null, beanName, c.getParameterTypes(),
                c.getGenericParameterTypes(), c.getParameterAnnotations());
        dependencies.put(beanName, new HashSet<>());
        boolean accessible = c.isAccessible();
        try {
            c.setAccessible(true);
            return convert(c.newInstance(parameters));
        } finally {
            c.setAccessible(accessible);
        } // try/finally
    } // createInstance()

    /**
     * Derives a bean name from an optional given name, the beans class and this classes annotations.
     *
     * @param name intended name - may be null
     * @param cls type of the bean
     * @return name to be used for the bean
     */
    private String getBeanName(Class<? extends Object> cls, String name) {
        LOG.debug("getBeanName({}) cls={}", name, cls);
        Named annotation = cls.getAnnotation(Named.class);
        String beanName = (name == null) ? (annotation == null ? null : annotation.value()) : name;
        if (StringUtils.isBlank(beanName)) {
            beanName = Introspector.decapitalize(cls.getSimpleName());
        } // if
        return beanName;
    } //  getBeanName()

    /**
     * Creates an instance of the given type and registeres it with the container.
     *
     * @param cls type to create an instance of
     * @param name optional name - if null the name is taken from the at Named annotation or from the class name otherwise
     * @throws Exception when instanciation is not possible for whatever reason
     */
    private void createAndRegisterInstance(Map<String, Set<Object>> dependencies, Class<? extends Object> cls,
            String name) throws Exception {
        LOG.info("createAndRegisterInstance({}) cls={}", name, cls);
        String beanName = getBeanName(cls, name);
        Object bean = createInstance(dependencies, cls, beanName);
        beans.put(beanName, bean);
    } // createAndRegisterInstance()

    /**
     * Calls a method annotated as post construct on a given bean if available.
     *
     * @param bean bean to check and call post contruct annotated method on
     * @throws SecurityException
     */
    private void callPostConstruct(Object bean) throws SecurityException {
        for (Method m : bean.getClass().getMethods()) {
            if (m.getAnnotation(PostConstruct.class) != null) {
                LOG.info("() post construct method on {}: {}", bean, m.getName());
                try {
                    m.invoke(bean, new Object[0]);
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                    LOG.error("() error calling post constructor " + m.getName() + " at " + bean + " :"
                            + bean.getClass().getName(), ex);
                } // try/catch
            } // if
        } // for
    } // callPostConstruct()

    /**
     * Create a fresh instance of a given class and inject all needed dependencies from the dinistiq scope.
     *
     * @param <T>
     * @param cls
     * @param name an optional name of the beans used for injection discovery - may be null
     * @return fresh instance with dependencies filled in and post contruct method called if available
     */
    public <T extends Object> T createBean(Class<T> cls, String name) {
        try {
            String beanName = getBeanName(cls, name);
            Map<String, Set<Object>> dependencies = new HashMap<>();
            T bean = createInstance(dependencies, cls, beanName);
            injectDependencies(dependencies, beanName, bean);
            callPostConstruct(bean);
            return bean;
        } catch (Exception e) {
            return null;
        } // try/catch
    } //  createBean()

    /**
     * Initialize a fresh instance by injecting all needed dependencies from the dinistiq scope.
     *
     * @param bean externally created bean with missing dependencies
     * @param name an optional name of the bean used for injection discovery - may be null
     */
    public void initBean(Object bean, String name) {
        try {
            String beanName = getBeanName(bean.getClass(), name);
            Map<String, Set<Object>> dependencies = new HashMap<>();
            dependencies.put(beanName, new HashSet<>());
            injectDependencies(dependencies, beanName, bean);
            callPostConstruct(bean);
        } catch (Exception e) {
            LOG.error("initBean() " + bean.getClass(), e);
        } // try/catch
    } //  initBean()

    /**
     * Get properties according to standard directory scheme from defaults and specialized properties for a given key.
     *
     * @param key key resembling the properties file name to look for in dinistiq/defaults and dinistiq/beans
     * resources folder
     * @return map collected from defaults and specialized values
     * @throws IOException
     */
    private Properties getProperties(String key) throws IOException {
        Properties beanProperties = new Properties();
        final String defaultsName = PRODUCT_BASE_PATH + "/defaults/" + key + ".properties";
        final Enumeration<URL> resources = Thread.currentThread().getContextClassLoader()
                .getResources(defaultsName);
        LOG.debug("getProperties({}) searching defaults {} {}", key, defaultsName, resources);
        while (resources.hasMoreElements()) {
            final URL resource = resources.nextElement();
            LOG.debug("getProperties({}) loading defaults from {}", key, defaultsName);
            beanProperties.load(resource.openStream());
        } // while
        final String beanValuesName = PRODUCT_BASE_PATH + "/beans/" + key + ".properties";
        InputStream beanStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(beanValuesName);
        LOG.debug("getProperties({}) searching bean values {} {}", key, beanValuesName, beanStream);
        if (beanStream != null) {
            LOG.debug("getProperties({}) loading bean values from {}", key, beanValuesName);
            beanProperties.load(beanStream);
        } // if
        return beanProperties;
    } // getProperties()

    /**
     * Tries to convert the given value as an object reference or string pattern replacement.
     *
     * @param propertyValue
     * @return referenced object or original string if unavailable
     */
    private Object getReferenceValue(String propertyValue) {
        Object result = propertyValue;
        if (propertyValue.startsWith("${")) {
            String referenceName = propertyValue.substring(2, propertyValue.length() - 1);
            LOG.debug("getReferenceValue({}) {}", propertyValue, referenceName);
            result = beans.containsKey(referenceName) ? beans.get(referenceName) : result;
        } // if
        if (result instanceof String) {
            String stringValue = (String) result;
            Pattern p = REPLACEMENT_PATTERN;
            Matcher m = p.matcher(stringValue);
            while (m.find()) {
                LOG.debug("getReferenceValue({}) string replacement in {}", propertyValue, stringValue);
                String name = m.group();
                name = name.substring(2, name.length() - 1);
                LOG.debug("getReferenceValue({}) replacing {}", propertyValue, name);
                String replacement = beans.containsKey(name) ? "" + beans.get(name)
                        : (environment.containsKey(name) ? environment.get(name) : "__UNKNOWN__");
                stringValue = stringValue.replace("${" + name + "}", replacement);
                m = p.matcher(stringValue);
            } // while
            result = stringValue;
        } // if
        return result;
    } // getReferenceValue()

    /**
     * Store URL parts of a given named value with suffixed names in a given map of config values.
     *
     * @param name base name of the parts
     * @param value original value of the property
     * @param values map to store split values in
     */
    private void storeUrlParts(String name, String value, Map<String, Object> values) {
        // split
        int idx = value.indexOf("://");
        if ((idx > 0) && (value.length() > idx + 5)) {
            // Might be a URL
            try {
                LOG.info("storeUrlParts() splitting {} ({})", value, name);
                String protocol = value.substring(0, idx);
                values.put(name + ".protocol", protocol);
                idx += 3;
                String host = value.substring(idx);
                String uri = "";
                idx = host.indexOf('/');
                if (idx > 0) {
                    uri = host.substring(idx + 1);
                    host = host.substring(0, idx);
                } // if
                if (StringUtils.isNotBlank(uri)) {
                    values.put(name + ".uri", uri);
                } // if
                String username = "";
                idx = host.indexOf('@');
                if (idx > 0) {
                    username = host.substring(0, idx);
                    host = host.substring(idx + 1);
                } // if
                String[] userinfos = username.split(":");
                if (userinfos.length > 1) {
                    username = userinfos[0];
                    values.put(name + ".password", userinfos[1]);
                } // if
                if (StringUtils.isNotBlank(username)) {
                    values.put(name + ".username", username);
                } // if

                String port = "";
                idx = host.indexOf(':');
                if (idx > 0) {
                    port = host.substring(idx + 1);
                    host = host.substring(0, idx);
                } // if
                values.put(name + ".host", host);
                if (StringUtils.isNotBlank(port)) {
                    values.put(name + ".port", port);
                } // if
            } catch (Exception e) {
                LOG.error("storeUrlParts() error reading " + value + " as a url", e);
            } // try/catch
        } // if
    } // storeUrlParts()

    /**
     * Fill bean as a map.
     * Replaces object references but does not split compound values like sets or lists.
     *
     * @param bean must be of type Map<Object, Object>
     * @param mapProperties properties map with the values to be added to the map bean
     */
    @SuppressWarnings("unchecked")
    private void fillMap(Object bean, Properties mapProperties) {
        Map<Object, Object> map = (Map<Object, Object>) bean;
        for (String name : mapProperties.stringPropertyNames()) {
            map.put(name, getReferenceValue(mapProperties.getProperty(name)));
        } // while
    } // fillMap

    /**
     * Injects all available dependencies into a given bean and records all dependencies.
     *
     * @param key key / name/ id of the bean
     * @param bean bean instance
     * @param dependencies dependencies map where the dependecies of the bean are recorded with the given key
     * @throws Exception
     */
    private void injectDependencies(Map<String, Set<Object>> dependencies, String key, Object bean)
            throws Exception {
        // Prepare values from properties files
        Properties beanProperties = getProperties(key);
        LOG.debug("injectDependencies({}) bean properties {}", key, beanProperties.keySet());

        // fill injected fields
        Class<? extends Object> beanClass = bean.getClass();
        String beanClassName = beanClass.getName();
        while (beanClass != Object.class) {
            if (bean instanceof Map) {
                fillMap(bean, getProperties(key));
                LOG.info("injectDependencies() filled map '{}' {}", key, bean);
                return; // If it's a map we don't need to inject anything beyond some map properties files.
            } // if
            for (Field field : beanClass.getDeclaredFields()) {
                LOG.debug("injectDependencies({}) field {}", key, field.getName());
                if (field.getAnnotation(Inject.class) != null) {
                    Named named = field.getAnnotation(Named.class);
                    String name = (named == null) ? null
                            : (StringUtils.isBlank(named.value()) ? field.getName() : named.value());
                    LOG.info("injectDependencies({}) {} :{} needs injection with name {}", key, field.getName(),
                            field.getGenericType(), name);
                    Object b = getValue(beanProperties, dependencies, key, field.getType(), field.getGenericType(),
                            name);
                    final boolean accessible = field.isAccessible();
                    try {
                        field.setAccessible(true);
                        field.set(bean, b);
                    } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
                        LOG.error("injectDependencies() error setting field " + field.getName() + " :"
                                + field.getType().getName() + " at '" + key + "' :" + beanClassName, e);
                    } finally {
                        field.setAccessible(accessible);
                    } // try/catch
                } // if
            } // for
            beanClass = beanClass.getSuperclass();
        } // while

        // call methods with annotated injections
        for (Method m : bean.getClass().getMethods()) {
            if (m.getAnnotation(Inject.class) != null) {
                LOG.debug("injectDependencies({}) inject parameters on method {}", key, m.getName());
                Class<? extends Object>[] parameterTypes = m.getParameterTypes();
                Type[] genericParameterTypes = m.getGenericParameterTypes();
                Annotation[][] parameterAnnotations = m.getParameterAnnotations();
                Object[] parameters = getParameters(beanProperties, dependencies, key, parameterTypes,
                        genericParameterTypes, parameterAnnotations);
                try {
                    m.invoke(bean, parameters);
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                    LOG.error("injectDependencies() error injecting for method " + m.getName() + " at '" + key
                            + "' :" + beanClassName, ex);
                } // try/catch
            } // if
        } // for

        // Fill in manually set values from properties file
        for (String property : beanProperties.stringPropertyNames()) {
            String methodName = "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
            LOG.debug("injectDependencies({}) {} -> {}", key, property, methodName);
            Method m = null;
            // Have to find it just by name
            for (Method me : bean.getClass().getMethods()) {
                if (me.getName().equals(methodName) && (me.getParameterTypes().length > 0)) {
                    m = me;
                } // if
            } // for
            if (m == null) {
                LOG.warn("injectDependencies({}) no setter method found for property {}", key, property);
            } else {
                String propertyName = Introspector.decapitalize(m.getName().substring(3));
                Class<?> parameterType = m.getParameterTypes()[0];
                Type genericType = m.getGenericParameterTypes()[0];
                LOG.debug("injectDependencies({}) writable property found {} :{} {}", key, propertyName,
                        parameterType, genericType);
                String propertyValue = beanProperties.getProperty(propertyName); // Must definetely be there without additional check
                boolean isBoolean = (parameterType == Boolean.class) || (m.getParameterTypes()[0] == Boolean.TYPE);
                boolean isCollection = Collection.class.isAssignableFrom(parameterType);
                Object[] parameters = new Object[1];
                LOG.debug("injectDependencies({}) trying to set value {} (bool {}) (collection {}) '{}'", key,
                        propertyName, isBoolean, isCollection, propertyValue);
                try {
                    parameters[0] = getReferenceValue(propertyValue);
                    if (isBoolean && (parameters[0] instanceof String)) {
                        parameters[0] = Boolean.valueOf(propertyValue);
                    } // if
                    if ("long".equals(parameterType.getName())) {
                        parameters[0] = new Long(propertyValue);
                    } // if
                    if ("int".equals(parameterType.getName())) {
                        parameters[0] = new Integer(propertyValue);
                    } // if
                    if ("float".equals(parameterType.getName())) {
                        parameters[0] = new Float(propertyValue);
                    } // if
                    if ("double".equals(parameterType.getName())) {
                        parameters[0] = new Double(propertyValue);
                    } // if
                    if (isCollection) {
                        if (!Collection.class.isAssignableFrom(parameters[0].getClass())) {
                            Collection<Object> values = List.class.isAssignableFrom(parameterType)
                                    ? new ArrayList<>()
                                    : new HashSet<>();
                            for (String value : propertyValue.split(",")) {
                                values.add(getReferenceValue(value));
                            } // for
                            parameters[0] = values;
                        } // if
                        if (dependencies != null) {
                            for (Object d : (Collection<?>) parameters[0]) {
                                if (beans.containsValue(d)) {
                                    dependencies.get(key).add(d);
                                } // if
                            } // if
                        } // if
                    } else {
                        if ((dependencies != null) && (beans.containsValue(parameters[0]))) {
                            dependencies.get(key).add(parameters[0]);
                        } // if
                    } // if
                    LOG.debug("injectDependencies({}) setting value {} '{}' :{}", key, propertyName, parameters[0],
                            parameters[0].getClass());
                    m.invoke(bean, parameters);
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                    LOG.error("injectDependencies() error setting property " + propertyName + " to '"
                            + propertyValue + "' at " + key + " :" + beanClassName, ex);
                } // try/catch
            } // if
        } // for
    } // injectDependencies()

    /**
     * Create a dinistiq context from the given class resolver and optional external beans.
     * Add all the external named beans from thei given map for later lookup to the context as well
     * and be sure that your class resolver takes the resources in the dinistiq/ path of your
     * class path into cosideration.
     *
     * @param classResolver resolver to us when resolving all types of classes
     * @param externalBeans map of beans with their id (name) as the key
     * @throws java.lang.Exception thrown with a readable message if something goes wrong
     */
    public Dinistiq(ClassResolver classResolver, Map<String, Object> externalBeans) throws Exception {
        // measure time for init process
        long start = System.currentTimeMillis();

        Map<String, Set<Object>> dependencies = new HashMap<>();

        // Use all externally provided beans
        if (externalBeans != null) {
            beans.putAll(externalBeans);
            for (String externalBeanName : externalBeans.keySet()) {
                dependencies.put(externalBeanName, new HashSet<>());
            } // for
        } // if

        // Add system properties to scope and split potential URL values
        for (Object keyObject : System.getProperties().keySet()) {
            String key = keyObject.toString();
            beans.put(key, System.getProperty(key));
            storeUrlParts(key, System.getProperty(key), beans);
        } // for
          // Add environment to scope and split potential URL values
        for (String key : environment.keySet()) {
            storeUrlParts(key, environment.get(key), beans);
        } // for
        LOG.debug("() initial beans {}", beans);

        // Read bean list from properties files mapping names to names of the classes to be instanciated
        Properties beanlist = new Properties();
        SortedSet<String> propertiesFilenames = classResolver.getProperties(PRODUCT_BASE_PATH + "/");
        LOG.debug("() checking {} files for properties", propertiesFilenames.size());
        for (String propertyResource : propertiesFilenames) {
            LOG.debug("() check {}", propertyResource);
            // ignore subfolders!
            if (propertyResource.indexOf('/', PRODUCT_BASE_PATH.length() + 1) < 0) {
                LOG.debug("() resource {}", propertyResource);
                beanlist.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(propertyResource));
            } // if
        } // for
        List<Class<?>> classList = new ArrayList<>();
        List<String> nameList = new ArrayList<>();
        for (String key : beanlist.stringPropertyNames()) {
            String className = beanlist.getProperty(key);
            if (MAP_TYPE.equals(className)) {
                beans.put(key, new HashMap<>());
                dependencies.put(key, new HashSet<>());
            } else {
                // expect java.lang.Xyz("value")
                int idx = className.indexOf('(');
                if (className.startsWith(JAVALANG_PREFIX) && (idx > 0)) {
                    String value = getReferenceValue(className.substring(idx + 2, className.length() - 2))
                            .toString();
                    className = className.substring(0, idx);
                    LOG.debug("() instanciating {} :{}", value, className);
                    Class<? extends Object> c = Class.forName(className.substring(0, idx));
                    Object instance = c.getConstructor(String.class).newInstance(value);
                    LOG.info("() storing value {} :{} - {}", key, instance.getClass().getName(), instance);
                    beans.put(key, instance);
                    dependencies.put(key, new HashSet<>());
                } else {
                    boolean setType = className.startsWith(SET_TYPE);
                    if ((setType || className.startsWith(LIST_TYPE)) && (idx > 0)) {
                        String values[] = getReferenceValue(className.substring(idx + 1, className.length() - 1))
                                .toString().split(",");
                        Collection<String> instance = setType ? new HashSet<>(Arrays.asList(values))
                                : Arrays.asList(values);
                        LOG.debug("() collection {} (set {}): {}", key, setType, instance);
                        beans.put(key, instance);
                        dependencies.put(key, new HashSet<>());
                    } else {
                        LOG.debug("() listing {}", className);
                        Class<? extends Object> c = Class.forName(className);
                        classList.add(c);
                        nameList.add(key);
                    } // if
                } // if
            } // if
        } // for
        LOG.info("() beanlist {}", beanlist);

        // List annotated beans
        final Set<Class<Object>> classes = classResolver.getAnnotated(Singleton.class);
        LOG.info("() number of annotated beans {}", classes.size());
        for (Class<? extends Object> c : classes) {
            classList.add(c);
            nameList.add(null);
        } // for
        LOG.debug("() beans {}", beans.keySet());

        // Instanciate beans from the properties files and from annotations taking constructor injection dependencies into account
        int ripCord = 10;
        while ((ripCord > 0) && (!classList.isEmpty())) {
            LOG.debug("() trying {} beans: {}", nameList.size(), classList);
            ripCord--;
            List<Class<?>> restClassList = new ArrayList<>();
            List<String> restNameList = new ArrayList<>();
            for (int i = 0; i < classList.size(); i++) {
                try {
                    createAndRegisterInstance(dependencies, classList.get(i), nameList.get(i));
                } catch (Exception e) {
                    LOG.warn("() will retry {} later: {} - {}", classList.get(i), e.getClass().getName(),
                            e.getMessage());
                    restClassList.add(classList.get(i));
                    restNameList.add(nameList.get(i));
                } // try/catch
            } // for
            classList = restClassList;
            nameList = restNameList;
        } // while

        // Fill in injections and note needed dependencies
        for (String key : new HashSet<>(beans.keySet())) {
            injectDependencies(dependencies, key, beans.get(key));
        } // for

        // sort beans according to dependencies
        LOG.info("() sorting beans according to dependencies");
        ripCord = 10;
        while ((ripCord > 0) && (!dependencies.isEmpty())) {
            ripCord--;
            LOG.info("() {} beans left", dependencies.size());
            Set<String> deletions = new HashSet<>();
            for (String key : dependencies.keySet()) {
                LOG.debug("() checking if {} with {} dependencies can be safely put into the ordered list {}", key,
                        dependencies.get(key).size(), dependencies.get(key));
                boolean dependenciesMet = true;
                for (Object dep : dependencies.get(key)) {
                    boolean isMet = orderedBeans.contains(dep);
                    LOG.debug("() {} depends on {} :{} missing? {} collection= {}", key, dep,
                            dep.getClass().getName(), !isMet, (dep instanceof Collection));
                    dependenciesMet = dependenciesMet && isMet;
                } // for
                if (dependenciesMet) {
                    LOG.info("() adding {} to the list {}", key, orderedBeans);
                    orderedBeans.add(beans.get(key));
                    deletions.add(key);
                } // if
            } // for
            for (String key : deletions) {
                dependencies.remove(key);
            } // for
        } // while
        if (dependencies.size() > 0) {
            throw new Exception("Circular bean injection and initialization dependencies detected after "
                    + (System.currentTimeMillis() - start) + "ms" + " " + dependencies);
        } // if

        // Call Post Construct
        LOG.info("() calling post construct on ordered beans {}", orderedBeans);
        for (Object bean : orderedBeans) {
            LOG.info("() bean {}", bean);
            callPostConstruct(bean);
        } // for
        LOG.info("() calling post construct for the rest of the beans");
        for (String key : beans.keySet()) {
            Object bean = beans.get(key);
            if (!orderedBeans.contains(bean) && !String.class.isAssignableFrom(bean.getClass())) {
                LOG.warn("() bean without dependencies to call post construct method on {} :{}", key,
                        bean.getClass().getSimpleName());
                callPostConstruct(bean);
            } // if
        } // for
        LOG.info("() setup completed after {}ms", (System.currentTimeMillis() - start));
    } // Dinistiq()

    /**
     * Create a dinistiq context from the given packages set and the config files placed in the dinistiq/
     * substructure of the resource path.
     * Add all the external named beans from thei given map for later lookup to the context as well.
     *
     * @param packages set of java package names
     * @param externalBeans Map of beans providded externally with their respective id (name) as the key
     * @throws Exception thrown when anything goes wrong with a readable message
     */
    public Dinistiq(Set<String> packages, Map<String, Object> externalBeans) throws Exception {
        this(new SimpleClassResolver(packages), externalBeans);
    } // Dinistiq()()

    /**
     * Create a dinistiq context from the given packages set and the config files placed in the dinistiq/
     * substructure of the resource path.
     *
     * @param packages set of java package names
     * @throws Exception thrown when anything goes wrong with a readable message
     */
    public Dinistiq(Set<String> packages) throws Exception {
        this(packages, null);
    } // Dinistiq()

} // Dinistiq