Skip navigation
2012

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

So the next thing I wanted to do was to tie into the shutdown of the Weld framework, and the way to do that is via an javax.enterprise.inject.spi.Extension implementation. The default way to add an Extension is to use the META-INF/services mechanism, but since the custom startup class had access to the Weld instance, and this has an addExtension method, I leveraged that along with a couple of new annotations to have the custom startup handle all of the details. The new classes are:

 

package com.si.weld;


import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;


import javax.enterprise.inject.spi.Extension;
import javax.enterprise.util.AnnotationLiteral;
import java.lang.annotation.Annotation;


/**
 * A weld startup class for use in Java SE environment
 *
 * @author Scott Stark
 * @version $Revision:$
 */
public class CustomWeldStartMain {


    /**
     * The entry point to the weld initialization
     * @param args - the
     *             [0] = the class name of WeldMain class to bootstrap
     *             [1..n] = the args to pass to the WeldMain.main(String...) method
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        // Need at least one arg giving the WeldMain implementation class name
        if(args.length == 0) {
            throw new IllegalStateException("Non-zero arguments required, first argument must be main class name");
        }
        // Load the class to use as the main class
        String mainClassName = args[0];
        Class<?> mainClass = CustomWeldStartMain.class.getClassLoader().loadClass(mainClassName);
        if(WeldMain.class.isAssignableFrom(mainClass) == false) {
            throw new IllegalStateException(mainClassName+"does not implement WeldMain");
        }
        Class<WeldMain> weldMainClass = (Class<WeldMain>) mainClass;
        Weld weld = new Weld();
        // See if there is a main class extension
        Extension mainExtension = loadExtension(weldMainClass, weld);
        if(mainExtension instanceof DefaultWeldMainExtension) {
            // This is a hack to tie into extension to know which WeldMain impl was used
            DefaultWeldMainExtension tme = (DefaultWeldMainExtension) mainExtension;
            tme.setWeldMainClass(weldMainClass);
        }
        // Standard Weld bootstrap from org.jboss.weld.environment.se.StartMain
        WeldContainer weldContainer = weld.initialize();
        Annotation qualifier = new AnnotationLiteral<WeldMainType>() {};
        WeldMain main = weldContainer.instance().select(weldMainClass, qualifier).get();


        // Add the SE shutdown hook
        Runtime.getRuntime().addShutdownHook(new ShutdownHook(weld));
        // Call the WeldMain.main() entry point
        String[] subargs = new String[args.length-1];
        System.arraycopy(args, 1, subargs, 0, args.length-1);
        main.main(subargs);
    }


    /**
     * Look for an Extension implementation via a WeldMainExtension on the weldMainClass
     * @param weldMainClass
     * @param weld
     */
    private static Extension loadExtension(Class<WeldMain> weldMainClass, Weld weld) {
        Extension extension = null;
        WeldMainExtension extensionType = weldMainClass.getAnnotation(WeldMainExtension.class);
        if(extensionType != null) {
            try {
                Class<? extends Extension> c = extensionType.value();
                extension = c.newInstance();
                weld.addExtension(extension);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return extension;
    }


    static class ShutdownHook extends Thread {
        private final Weld weld;


        ShutdownHook(final Weld weld) {
            this.weld = weld;
        }


        public void run() {
            weld.shutdown();
        }
    }
}

package com.si.weld;


/**
 * A simple interface defining the Weld post bootstrap main entry point.
 *
 * @author Scott Stark
 * @version $Revision:$
 */
public interface WeldMain {
    public void main(String[] args) throws Exception;
    public void shutdown();
}

package com.si.weld;


import javax.enterprise.inject.spi.Extension;
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.*;


/**
 * A WeldMainExtension qualifier
 * @author Scott Stark
 * @version $Revision:$
 */
@Qualifier
@Target({TYPE})
@Retention(RUNTIME)
public @interface WeldMainExtension {
    Class<? extends Extension> value();
}

package com.si.weld;


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.*;


/**
 * A WeldMain qualifier used to identify which WeldMain bean was used
 * @author Scott Stark
 * @version $Revision:$
 */
@Qualifier
@Target({TYPE})
@Retention(RUNTIME)
public @interface WeldMainType {
}

package com.si.weld;


import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.UnsatisfiedResolutionException;
import javax.enterprise.inject.spi.AnnotatedConstructor;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeShutdown;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.util.AnnotationLiteral;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;


/**
 * A Weld Extension that marks the ManagedBean used as the WeldMain instance with a
 * WeldMainType qualifier to allow it to be selected during shutdown, and its WeldMain.shutdown
 * method called.
 * @author Scott Stark
 * @version $Revision:$
 */
public class DefaultWeldMainExtension implements Extension {
    private Class<WeldMain> weldMainClass;
    private HashSet<WeldMainAnnotatedType> weldMainTypes = new HashSet<WeldMainAnnotatedType>();


    <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> pat) {
        AnnotatedType<T> atype = pat.getAnnotatedType();
        if(WeldMain.class.isAssignableFrom(atype.getJavaClass())) {
            //System.out.printf("TestMainExtension: scanning type: %s\n", atype.getJavaClass().getName());
            // If this is a WeldMain, add a wrapper to later hold a WeldMainType qualifier
            if(atype.getJavaClass().equals(weldMainClass)) {
                WeldMainAnnotatedType wrapped = new WeldMainAnnotatedType(atype);
                weldMainTypes.add(wrapped);
                pat.setAnnotatedType(wrapped);
            }
        }
    }
    void beforeShutdown(@Observes BeforeShutdown shutdown, final BeanManager beanManager){
        // Find the WeldMain to invoke shutdown on
        Annotation qualifier = new AnnotationLiteral<WeldMainType>() {};
        WeldMain main = getInstanceByType(beanManager, WeldMain.class, qualifier);
        main.shutdown();
    }


    protected <T> T getInstanceByType(BeanManager manager, Class<T> type, Annotation... bindings) {
        Set<Bean<?>> beans = manager.getBeans(type, bindings);
        final Bean<?> bean = manager.resolve(beans);
        if (bean == null) {
            throw new UnsatisfiedResolutionException("Unable to resolve a bean for " + type + " with bindings " + Arrays.asList(bindings));
        }
        CreationalContext<?> cc = manager.createCreationalContext(bean);
        return type.cast(manager.getReference(bean, type, cc));
    }


    public void setWeldMainClass(Class<WeldMain> weldMainClass) {
        this.weldMainClass = weldMainClass;
    }


    private static class WeldMainAnnotatedType<T> implements AnnotatedType<T> {
        private final AnnotatedType<T> type;
        private boolean markAsWeldMainType = true;
        WeldMainAnnotatedType(AnnotatedType<T> type){
            this.type = type;
        }


        @Override
        public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
           return annotationType.equals(WeldMainType.class) ?
                   markAsWeldMainType : type.isAnnotationPresent(annotationType);
        }


        @Override
        public Set<AnnotatedConstructor<T>> getConstructors() {
            return type.getConstructors();
        }


        @Override
        public Set<AnnotatedField<? super T>> getFields() {
            return type.getFields();
        }


        @Override
        public Class<T> getJavaClass() {
            return type.getJavaClass();
        }


        @Override
        public Set<AnnotatedMethod<? super T>> getMethods() {
            return type.getMethods();
        }


        @Override
        public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
            return type.getAnnotation(annotationType);
        }


        @Override
        public Set<Annotation> getAnnotations() {
            HashSet<Annotation> annotations = new HashSet<Annotation>(type.getAnnotations());
            if(markAsWeldMainType) {
                annotations.add(new AnnotationLiteral<WeldMainType>() {});
            }
            return annotations;
        }


        @Override
        public Type getBaseType() {
            return type.getBaseType();
        }


        @Override
        public Set<Type> getTypeClosure() {
            return type.getTypeClosure();
        }
    }


}

 

The new sample TestMain becomes:

 

package test.com.si.weld;

import com.si.weld.DefaultWeldMainExtension;
import com.si.weld.WeldMain;
import com.si.weld.WeldMainExtension;

import javax.enterprise.inject.Default;
import javax.inject.Singleton;
import java.util.Arrays;


/**
 * A minimalist WeldMain implementation
 *
 * @author Scott Stark
 * @version $Revision:$
 */
@Singleton
@Default
@WeldMainExtension(DefaultWeldMainExtension.class)
public class TestMain2 implements WeldMain {
    public TestMain2() {
    }


    public void main(String[] args) {
        System.out.printf("TestMain2.main(%s)\n", Arrays.asList(args));
    }


    @Override
    public void shutdown() {
        System.out.printf("TestMain2.shutdown()\n");
    }
}

 

 

Which when run using the CustomWeldStartMain main entry point produces:

 

/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/bin/java -Didea.launcher.port=7537 "..." com.intellij.rt.execution.application.AppMain com.si.weld.CustomWeldStartMain test.com.si.weld.TestMain2 arg2 arg3
34 [main] INFO org.jboss.weld.Version - WELD-000900 1.1.9 (Final)
296 [main] INFO org.jboss.weld.Bootstrap - WELD-000101 Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
TestMain2.main([arg2, arg3])
TestMain2.shutdown()


Process finished with exit code 0

 

 

 

It was a whole lot more code just to add support for the invocation of the WeldMain.shutdown method, and most of that had to do with getting a qualifier on the ManagedBean that was used as the WeldMain implementation. Perhaps there is an easier way.

I was working with Weld framework in a Java SE environment largely through the IDE, and had found posts about using the org.jboss.weld.environment.se.StartMain class to bootstrap Weld from within Java SE, but I wanted more control over which class was targeted as the prescribed approach of having a ContainerInitialized event listener ala:

 

public class TestMain {
    public void main(@Observes ContainerInitialized event, @Parameters List parameters) {
        System.out.printf("TestMain.main called, parameters=%s\n", parameters);
    }
}

 

would result in every such listener in the ide classpath being called. I took the simple StartMain bootstrap code and created the following CustomWeldStartMain that accepts the name of the class to use as the post bootstrap entry point:

 

package com.si.weld;


import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;


/**
 * A weld startup class for use in Java SE environment
 *
 * @author Scott Stark
 * @version $Revision:$
 */
public class CustomWeldStartMain {


    /**
     * The entry point to the weld initialization
     * @param args - the
     *             [0] = the class name of WeldMain class to bootstrap
     *             [1..n] = the args to pass to the WeldMain.main(String...) method
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        // Need at least one arg giving the WeldMain implementation class name
        if(args.length == 0) {
            throw new IllegalStateException("Non-zero arguments required, first argument must be main class name");
        }
        // Load the class to use as the main class
        String mainClassName = args[0];
        Class<?> mainClass = CustomWeldStartMain.class.getClassLoader().loadClass(mainClassName);
        if(WeldMain.class.isAssignableFrom(mainClass) == false) {
            throw new IllegalStateException(mainClassName+"does not implement WeldMain");
        }
        Class<WeldMain> weldMainClass = (Class<WeldMain>) mainClass;
        // Standard Weld bootstrap from org.jboss.weld.environment.se.StartMain
        Weld weld = new Weld();
        WeldContainer weldContainer = weld.initialize();
        WeldMain main = weldContainer.instance().select(weldMainClass).get();
        // Add the SE shutdown hook
        Runtime.getRuntime().addShutdownHook(new ShutdownHook(weld));
        // Call the WeldMain.main() entry point
        String[] subargs = new String[args.length-1];
        System.arraycopy(args, 1, subargs, 0, args.length-1);
        main.main(subargs);
    }


    static class ShutdownHook extends Thread {
        private final Weld weld;


        ShutdownHook(final Weld weld) {
            this.weld = weld;
        }


        public void run() {
            weld.shutdown();
        }
    }
}

package com.si.weld;


/**
 * A simple interface defining the Weld post bootstrap main entry point.
 * 
 * @author Scott Stark
 * @version $Revision:$
 */
public interface WeldMain {
    public void main(String[] args) throws Exception;
}

 

Here is a sample test WeldMain entry point that is invoked when running from within the ide using a

 

package test.com.si.weld;


import com.si.weld.WeldMain;


import javax.inject.Singleton;
import java.util.Arrays;


/**
 * A minimalist WeldMain implementation
 *
 * @author Scott Stark
 * @version $Revision:$
 */
@Singleton
public class TestMain2 implements WeldMain {
    public void main(String[] args) {
        System.out.printf("TestMain2.main(%s)\n", Arrays.asList(args));
    }
}

 

 

To run this class, I setup a run configuration that specified com.si.weld.CustomWeldStartMain as the main class, and included test.com.si.weld.TestMain2 as the first program argument, with arg2, arg3 as the second argument. Running this within the ide produced:

 

/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/bin/java -Didea.launcher.port=7537 "..." com.intellij.rt.execution.application.AppMain com.si.weld.CustomWeldStartMain test.com.si.weld.TestMain2 arg2 arg3

45 [main] INFO org.jboss.weld.Version - WELD-000900 1.1.9 (Final)

263 [main] INFO org.jboss.weld.Bootstrap - WELD-000101 Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.

TestMain2.main([arg2, arg3])

Process finished with exit code 0

 

Maybe this is of general interest as an alternative StartMain?

Filter Blog

By date:
By tag: