Someone asked about "Can I write a method producer method without knowing the type until runtime?" and I was looking at something similar, trying to figure out what needs and Extension, so I mocked a little solution that uses a general producer method based on a specific qualifier annotation and a wrapping parameterized interface. See the forum thread for the original question, and this possible approach. While this may not be as transparent and generic as the user was asking for, it is a pretty simple way to achieve some form of dynamic production of injection values.
1. Create an qualifier annotation ConfigType:
package test.com.si.weld.dynproducer; import javax.inject.Qualifier; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.*; /** * @author Scott Stark * @version $Revision:$ */ @Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER}) public @interface ConfigType { }
2. Create a simple parameterized interface and wrapper implementation to hold the user defined configuration class types:
package test.com.si.weld.dynproducer; public interface IStoreSettings<T> { T getSettings(); } package test.com.si.weld.dynproducer; public class StoreSettingsWrapper<T> implements IStoreSettings<T> { private T settings; StoreSettingsWrapper(T settings) { this.settings = settings; } @Override public T getSettings() { return settings; } @Override public String toString() { return settings.toString(); } }
3. Create a general producer method that takes an InjectionPoint:
package test.com.si.weld.dynproducer; import javax.enterprise.inject.Produces; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.InjectionPoint; import java.lang.reflect.Constructor; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; /** * @author Scott Stark * @version $Revision:$ */ public class GenericConfigFactory { @Produces @ConfigType public IStoreSettings configProducer(InjectionPoint ip, BeanManager beanManager) throws Exception { dumpInjectionPoint(ip); ParameterizedType type = (ParameterizedType) ip.getType(); Type[] typeArgs = type.getActualTypeArguments(); Class<?> settingsClass = (Class<?>) typeArgs[0]; Constructor ctor = settingsClass.getConstructor(); Object settings = ctor.newInstance(); StoreSettingsWrapper wrapper = new StoreSettingsWrapper(settings); return wrapper; } private void dumpInjectionPoint(InjectionPoint ip) { StringBuilder tmp = new StringBuilder("InjectionPoint"); tmp.append("\n\tgetAnnotated:"+ip.getAnnotated()); tmp.append(";\n\t getType:"+ip.getType()); ParameterizedType type = (ParameterizedType) ip.getType(); Type[] typeArgs = type.getActualTypeArguments(); Type rawType = type.getRawType(); tmp.append("\n\t\ttypeArgs: "+ Arrays.asList(typeArgs)); tmp.append("\n\t\trawType: "+ rawType); tmp.append(";\n\t getQualifiers:"+ip.getQualifiers()); tmp.append(";\n\t getBean:"+ip.getBean()); tmp.append(";\n\t getMember:"+ip.getMember()); tmp.append(";\n\t isDelegate:"+ip.isDelegate()); tmp.append(";\n\t isTransient:"+ip.isTransient()); System.out.println(tmp.toString()); } }
Now one can create different type of configuration objects and have them injected into a consumer:
package test.com.si.weld.dynproducer; public @interface Config { public String value(); } package test.com.si.weld.dynproducer; import javax.inject.Inject; public class ConfigUser { @Inject @ConfigType private IStoreSettings<MyDataStoreSettings> settings; @Inject @ConfigType private IStoreSettings<AnotherDataStoreSettings> settings2; @Override public String toString() { return "ConfigUser{" + "settings2=" + settings2 + ", settings=" + settings + '}'; } } package test.com.si.weld.dynproducer; import java.io.File; public class MyDataStoreSettings { @Config("number.of.threads.key") int numberOfThreads = 1; @Config("root.folder.key") File rootFolder = new File("/tmp"); @Override public String toString() { return "MyDataStoreSettings{" + "numberOfThreads=" + numberOfThreads + ", rootFolder=" + rootFolder + '}'; } } package test.com.si.weld.dynproducer; public class AnotherDataStoreSettings { @Config("thread.count.key") int threadCount = 2; @Config("tmp.path.key") String tmpPath = "/tmp"; @Override public String toString() { return "AnotherDataStoreSettings{" + "threadCount=" + threadCount + ", tmpPath=" + tmpPath + '}'; } }
A little Arquillian testcase illustrates the behavior:
package test.com.si.weld.dynproducer; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.Test; import org.junit.runner.RunWith; import javax.inject.Inject; @RunWith(Arquillian.class) public class DynProducerTest { @Deployment public static JavaArchive createDeployment() { JavaArchive archive = ShrinkWrap.create(JavaArchive.class) .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); archive.addPackages(true, "test/com/si/weld/dynproducer"); return archive; } @Inject ConfigUser configUser; @Test public void testDynProducer() { System.out.printf("configUser=%s\n", configUser); } }
Outputs:
34 [main] INFO org.jboss.weld.Version - WELD-000900 1.1.9 (Final) InjectionPoint getAnnotated:[field] @ConfigType @Inject private test.com.si.weld.dynproducer.ConfigUser.settings; getType:interface test.com.si.weld.dynproducer.IStoreSettings<class test.com.si.weld.dynproducer.MyDataStoreSettings> typeArgs: [class test.com.si.weld.dynproducer.MyDataStoreSettings] rawType: interface test.com.si.weld.dynproducer.IStoreSettings; getQualifiers:[@test.com.si.weld.dynproducer.ConfigType()]; getBean:Managed Bean [class test.com.si.weld.dynproducer.ConfigUser] with qualifiers [@Any @Default]; getMember:private test.com.si.weld.dynproducer.IStoreSettings test.com.si.weld.dynproducer.ConfigUser.settings; isDelegate:false; isTransient:false InjectionPoint getAnnotated:[field] @ConfigType @Inject private test.com.si.weld.dynproducer.ConfigUser.settings2; getType:interface test.com.si.weld.dynproducer.IStoreSettings<class test.com.si.weld.dynproducer.AnotherDataStoreSettings> typeArgs: [class test.com.si.weld.dynproducer.AnotherDataStoreSettings] rawType: interface test.com.si.weld.dynproducer.IStoreSettings; getQualifiers:[@test.com.si.weld.dynproducer.ConfigType()]; getBean:Managed Bean [class test.com.si.weld.dynproducer.ConfigUser] with qualifiers [@Any @Default]; getMember:private test.com.si.weld.dynproducer.IStoreSettings test.com.si.weld.dynproducer.ConfigUser.settings2; isDelegate:false; isTransient:false configUser=ConfigUser{settings2=AnotherDataStoreSettings{threadCount=2, tmpPath=/tmp}, settings=MyDataStoreSettings{numberOfThreads=1, rootFolder=/tmp}}
See the attached dynproducer.zip for the full source.
ironmaiden:SITesting starksm$ jar -tf dynproducer.zip
src/test/java/test/com/si/weld/dynproducer/
src/test/java/test/com/si/weld/dynproducer/AnotherDataStoreSettings.java
src/test/java/test/com/si/weld/dynproducer/Config.java
src/test/java/test/com/si/weld/dynproducer/ConfigType.java
src/test/java/test/com/si/weld/dynproducer/ConfigUser.java
src/test/java/test/com/si/weld/dynproducer/DynProducerTest.java
src/test/java/test/com/si/weld/dynproducer/GenericConfigFactory.java
src/test/java/test/com/si/weld/dynproducer/IStoreSettings.java
src/test/java/test/com/si/weld/dynproducer/MyDataStoreSettings.java
src/test/java/test/com/si/weld/dynproducer/StoreSettingsWrapper.java