All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.archaius.ConfigProxyFactory Maven / Gradle / Ivy

package com.netflix.archaius;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.inject.Inject;

import org.apache.commons.lang3.text.StrSubstitutor;

import com.netflix.archaius.api.Config;
import com.netflix.archaius.api.Decoder;
import com.netflix.archaius.api.Property;
import com.netflix.archaius.api.PropertyFactory;
import com.netflix.archaius.api.annotations.Configuration;
import com.netflix.archaius.api.annotations.DefaultValue;
import com.netflix.archaius.api.annotations.PropertyName;

/**
 * Factory for binding a configuration interface to properties in a {@link PropertyFactory}
 * instance.  Getter methods on the interface are mapped by naming convention
 * by the property name may be overridden using the @PropertyName annotation.
 * 
 * For example,
 * 
 * {@code 
 * {@literal @}Configuration(prefix="foo")
 * interface FooConfiguration {
 *    int getTimeout();     // maps to "foo.timeout"
 *    
 *    String getName();     // maps to "foo.name"
 * }
 * }
 * 
* * To create a proxy instance, *
 * {@code 
 * FooConfiguration fooConfiguration = configProxyFactory.newProxy(FooConfiguration.class);
 * }
 * 
* * To override the prefix in {@literal @}Configuration or provide a prefix when there is no * @Configuration annotation simply pass in a prefix in the call to newProxy. * *
 * {@code 
 * FooConfiguration fooConfiguration = configProxyFactory.newProxy(FooConfiguration.class, "otherprefix.foo");
 * }
 * 
* * By default all properties are dynamic and can therefore change from call to call. To make the * configuration static set the immutable attributes of @Configuration to true. * * Note that an application should normally have just one instance of ConfigProxyFactory * and PropertyFactory since PropertyFactory caches {@link com.netflix.archaius.api.Property} objects. * * @see {@literal }@Configuration */ public class ConfigProxyFactory { /** * The decoder is used for the purpose of decoding any @DefaultValue annotation */ private final Decoder decoder; private final PropertyFactory propertyFactory; private final Config config; @Inject public ConfigProxyFactory(Config config, Decoder decoder, PropertyFactory factory) { this.decoder = decoder; this.config = config; this.propertyFactory = factory; } public ConfigProxyFactory(Config config, PropertyFactory factory) { this(config, DefaultDecoder.INSTANCE, factory); } public ConfigProxyFactory(Config config) { this(config, DefaultPropertyFactory.from(config)); } /** * Create a proxy for the provided interface type for which all getter methods are bound * to a Property. * * @param type * @param config * @return */ public T newProxy(final Class type) { return newProxy(type, null); } private String derivePrefix(Configuration annot, String prefix) { if (prefix == null && annot != null) { prefix = annot.prefix(); if (prefix == null) { prefix = ""; } } if (prefix == null) return ""; if (prefix.endsWith(".") || prefix.isEmpty()) return prefix; return prefix + "."; } public T newProxy(final Class type, final String initialPrefix) { Configuration annot = type.getAnnotation(Configuration.class); return newProxy(type, initialPrefix, annot == null ? false : annot.immutable()); } /** * Encapsulated the invocation of a single method of the interface * @author elandau * * @param */ public static interface MethodInvoker { /** * Invoke the method with the provided arguments * @param args * @return */ T invoke(Object[] args); /** * Return the property key * @return */ String getKey(); } /** * Abstract method invoker that encapsulates a property * @author elandau * * @param */ private static abstract class PropertyMethodInvoker extends AbstractProperty implements MethodInvoker { public PropertyMethodInvoker(String key) { super(key); } @Override public T invoke(Object[] args) { return get(); } } @SuppressWarnings({ "rawtypes", "unchecked" }) T newProxy(final Class type, final String initialPrefix, boolean immutable) { Configuration annot = type.getAnnotation(Configuration.class); final String prefix = derivePrefix(annot, initialPrefix); // Iterate through all declared methods of the class looking for setter methods. // Each setter will be mapped to a Property for the property name: // prefix + lowerCamelCaseDerivedPropertyName final Map> invokers = new HashMap<>(); for (Method m : type.getMethods()) { final String verb; if (m.getName().startsWith("get")) { verb = "get"; } else if (m.getName().startsWith("is")) { verb = "is"; } else { verb = ""; } final DefaultValue defaultValue = m.getAnnotation(DefaultValue.class); final Class returnType = m.getReturnType(); final PropertyName nameAnnot = m.getAnnotation(PropertyName.class); final String propName = nameAnnot != null && nameAnnot.name() != null ? prefix + nameAnnot.name() : prefix + Character.toLowerCase(m.getName().charAt(verb.length())) + m.getName().substring(verb.length() + 1); // For sub-interfaces create a proxy instance where the same proxy instance is returned but its // methods can still return dynamic values if (returnType.isInterface()) { invokers.put(m, createInterfaceProperty(propName, newProxy(returnType, propName, immutable))); } else { if (m.getParameterTypes().length > 0) { invokers.put(m, new MethodInvoker() { @Override public Object invoke(Object[] args) { // Determine the actual property name by replacing with arguments using the argument index // to the method. For example, // @PropertyName(name="foo.${1}.${0}") // String getFooValue(String arg0, Integer arg1) // // called as getFooValue("bar", 1) would look for the property 'foo.1.bar' Map values = new HashMap<>(); for (int i = 0; i < args.length; i++) { values.put("" + i, args[i]); } String propName = new StrSubstitutor(values, "${", "}", '$').replace(nameAnnot.name()); return getPropertyWithDefault(returnType, propName, (defaultValue != null) ? defaultValue.value() : null); } R getPropertyWithDefault(Class type, String propName, String defaultValue) { return propertyFactory.getProperty(propName).asType(type, decoder.decode(type, defaultValue)).get(); } @Override public String getKey() { return propName; } }); } else if (immutable) { if (defaultValue != null) { invokers.put(m, createImmutablePropertyWithDefault(m.getReturnType(), propName, defaultValue.value())); } else { invokers.put(m, createImmutablePropertyWithDefault(m.getReturnType(), propName, null)); } } else { if (defaultValue != null) { invokers.put(m, createDynamicProperty(m.getReturnType(), propName, defaultValue.value())); } else { invokers.put(m, createDynamicProperty(m.getReturnType(), propName, null)); } } } } final InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvoker invoker = invokers.get(method); if (invoker != null) { return invoker.invoke(args); } if ("toString".equals(method.getName())) { StringBuilder sb = new StringBuilder(); sb.append(type.getSimpleName()).append("["); Iterator>> iter = invokers.entrySet().iterator(); while (iter.hasNext()) { MethodInvoker entry = iter.next().getValue(); sb.append(entry.getKey().substring(prefix.length())).append("='"); try { sb.append(entry.invoke(null)); } catch (Exception e) { sb.append(e.getMessage()); } sb.append("'"); if (iter.hasNext()) { sb.append(", "); } } sb.append("]"); return sb.toString(); } else { throw new NoSuchMethodError(method.getName() + " not found on interface " + type.getName()); } } }; return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, handler); } protected MethodInvoker createImmutablePropertyWithDefault(final Class type, final String propName, final String defaultValue) { return new PropertyMethodInvoker(propName) { private volatile T cached; @Override public T get() { if (cached == null) { cached = propertyFactory.getProperty(propName).asType(type, decoder.decode(type, defaultValue)).get(); } return cached; } }; } protected MethodInvoker createInterfaceProperty(String propName, final T proxy) { return new PropertyMethodInvoker(propName) { @Override public T get() { return proxy; } }; } protected MethodInvoker createDynamicProperty(final Class type, final String propName, final String defaultValue) { final Property prop = propertyFactory .getProperty(propName) .asType(type, defaultValue != null // This is a hack to force interpolation of the defaultValue assuming // that ther is never a property '*' ? decoder.decode(type, config.getString("*", defaultValue)) : null); return new MethodInvoker() { @Override public T invoke(Object[] args) { return prop.get(); } @Override public String getKey() { return prop.getKey(); } }; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy