config-caf/src/main/java/com/hpe/caf/api/CafConfigurationSource.java
private <T> T getCompleteConfig(final Class<T> configClass)
        throws ConfigurationException
    {
        T config = getConfig(configClass);
        for (final Field f : configClass.getDeclaredFields()) {
            if (f.isAnnotationPresent(Configuration.class)) {
                try {
                    Method setter = getMethod(f.getName(), configClass, PropertyDescriptor::getWriteMethod);
                    if (setter != null) {
                        setter.invoke(config, getCompleteConfig(f.getType()));
                    }
                } catch (final ConfigurationException e) {
                    LOG.debug("Didn't find any overriding configuration", e);
                } catch (final InvocationTargetException | IllegalAccessException e) {
                    incrementErrors();
                    throw new ConfigurationException("Failed to get complete configuration for " + configClass.getSimpleName(), e);
                }
            } else if (f.getType().equals(String.class) && f.isAnnotationPresent(Encrypted.class)) {
                try {
                    Method getter = getMethod(f.getName(), config.getClass(), PropertyDescriptor::getReadMethod);
                    Method setter = getMethod(f.getName(), config.getClass(), PropertyDescriptor::getWriteMethod);
                    if (getter != null && setter != null) {
                        final String configValue = (String) getter.invoke(config);
                        final String encryptedValue = isSubstitutorEnabled ? tokenSubstitutor(configValue) : configValue;
                        setter.invoke(config, getCipher().decrypt(encryptedValue));
                    }
                } catch (final CipherException | InvocationTargetException | IllegalAccessException e) {
                    throw new ConfigurationException("Failed to decrypt class fields", e);
                }
            } else if (isSubstitutorEnabled && f.getType().equals(String.class)) {
                try {
                    String propertyName = f.getName();
                    Method getter = getMethod(propertyName, config.getClass(), PropertyDescriptor::getReadMethod);
                    Method setter = getMethod(propertyName, config.getClass(), PropertyDescriptor::getWriteMethod);
                    if (getter != null && setter != null) {
                        // Property value may contain tokens that require substitution.
                        String propertyValueByToken = tokenSubstitutor((String) getter.invoke(config));
                        setter.invoke(config, propertyValueByToken);
                    }
                } catch (final InvocationTargetException | IllegalAccessException e) {
                    throw new ConfigurationException("Failed to get complete configuration for " + configClass.getSimpleName(), e);
                }
            }
        }
        return config;
    }
private <T> T getCompleteConfig(final Class<T> configClass)
        throws ConfigurationException
    {
        T config = getConfig(configClass);
        for (final Field f : configClass.getDeclaredFields()) {
            if (f.isAnnotationPresent(Configuration.class)) {
                try {
                    Method setter = getMethod(f.getName(), configClass, PropertyDescriptor::getWriteMethod);
                    if (setter != null) {
                        setter.invoke(config, getCompleteConfig(f.getType()));
                    }
                } catch (final ConfigurationException e) {
                    LOG.debug("Didn't find any overriding configuration", e);
                } catch (final InvocationTargetException | IllegalAccessException e) {
                    incrementErrors();
                    throw new ConfigurationException("Failed to get complete configuration for " + configClass.getSimpleName(), e);
                }
            } else if (f.getType().equals(String.class) && f.isAnnotationPresent(Encrypted.class)) {
                try {
                    Method getter = getMethod(f.getName(), config.getClass(), PropertyDescriptor::getReadMethod);
                    Method setter = getMethod(f.getName(), config.getClass(), PropertyDescriptor::getWriteMethod);
                    if (getter != null && setter != null) {
                        final String configValue = (String) getter.invoke(config);
                        final String encryptedValue = isSubstitutorEnabled ? tokenSubstitutor(configValue) : configValue;
                        setter.invoke(config, getCipher().decrypt(encryptedValue));
                    }
                } catch (final CipherException | InvocationTargetException | IllegalAccessException e) {
                    throw new ConfigurationException("Failed to decrypt class fields", e);
                }
            } else if (isSubstitutorEnabled && f.getType().equals(String.class)) {
                try {
                    String propertyName = f.getName();
                    Method getter = getMethod(propertyName, config.getClass(), PropertyDescriptor::getReadMethod);
                    Method setter = getMethod(propertyName, config.getClass(), PropertyDescriptor::getWriteMethod);
                    if (getter != null && setter != null) {
                        // Property value may contain tokens that require substitution.
                        String propertyValueByToken = tokenSubstitutor((String) getter.invoke(config));
                        setter.invoke(config, propertyValueByToken);
                    }
                } catch (final InvocationTargetException | IllegalAccessException e) {
                    throw new ConfigurationException("Failed to get complete configuration for " + configClass.getSimpleName(), e);
                }
            }
        }
        return config;
    }
This is the recursive entry point for acquiring a complete configuration class to return. Attempt to acquire a deserialised object
representing the configuration class requested, and analyse it for declared fields marked @Configuration. If any are found, the
method recursively calls itself until all configuration is satisfied.

@param configClass the class representing configuration to acquire
@param <T> the class representing configuration to acquire
@return the completed (at this level) configuration
@throws ConfigurationException if configuration cannot be acquired