Seiten

Sonntag, 13. April 2014

Java8 CompletableFuture for JavaFX and CDI Part II

The last part was describing how you could reach the goal to make the order of some dependent method calls independent. Now we will see, how we could use this for the combination of CDI and JavaFX.
If you want to have CDI managed Controllers inside your JavaFX application you have to deal with the two life cycles. The init from CDI and the init from JavaFX. If you are combining both technologies, you will have the problem that the order of the livecycle steps are not always in the same order. Depending from the technology that will start, the order of the init methods will change. For CDI it is the method with the annotation Postconstruct and for JavaFX the method initialize.
Well, lets see what you could do..
For this example I will start with the FXMLoader. Check the Method setControllerFactory. You will see, that after the creation of the Controller instance the method initInstance() will be called.
 
@Singleton
public class FXMLLoaderSingleton {

    private @Inject @CDILogger Logger logger;
    private @Inject Instance<CDIJavaFxBaseController> instance;

    private final ClassLoader cachingClassLoader = new FXClassLoader(FXMLLoader.getDefaultClassLoader());
    private final Map<Class, FXMLLoader> class2LoaderMap = new HashMap<Class, FXMLLoader>();

    public FXMLLoader getFXMLLoader(Class clazz) {
        final Map<Class, FXMLLoader> loaderMap = class2LoaderMap;
        final String name = clazz.getName();
        if (loaderMap.containsKey(clazz)) {
            if (logger.isDebugEnabled()) {
                logger.debug("fx loader fuer diese klasse schon in der map " + name);
            }
        } else {
            final String fxmlFileName = clazz.getSimpleName() + ".fxml";
            if (logger.isDebugEnabled()) {
                logger.debug("fxmlFileName -> " + fxmlFileName);
            }
            final URL resource = clazz.getResource(fxmlFileName);
//            FXMLLoader loader = new CDIFXMLLoader(resource);
            FXMLLoader loader = new FXMLLoader(resource);
            loader.setClassLoader(cachingClassLoader);
            loader.setControllerFactory(new Callback<Class<?>, Object>() {
                @Override
                public Object call(Class<?> param) {
                    final Class<JavaFXBaseController> p = (Class<JavaFXBaseController>) param;
                    final JavaFXBaseController controller = instance.select(p).get();
                    controller.initInstance(); //trigger async call
                    return controller;
                }
            });
            try {  
                final Class<?> aClass = Class.forName(clazz.getName() + "Controller");
                final CDIJavaFxBaseController call = (CDIJavaFxBaseController) loader.getControllerFactory().call(aClass);
                loader.setController(call);
            } catch (ClassNotFoundException e) {
                logger.error(e);
            }
            loaderMap.put(clazz, loader);
        }
        return loaderMap.get(clazz);
    }

    private FXMLLoaderSingleton() {
    }
}

The method initInstance() will trigger the init-process. Inside the method the task will be called async. The task itself will wait until the CDi and JavaFX init will be ready. After this the method initBusinessLogic will be called. This means, thet the developer only will have to implement the method initBusinessLogic and he will be sure that all init stuff is done.
Happy coding ;-)
public abstract class JavaFXBaseController implements CDIJavaFxBaseController {

    public static final String DONE = "done";

    private boolean mockModusActive = false;
    public boolean isMockModusActive() {
        return mockModusActive;
    }
    public void setMockModusActive(boolean mockModusActive) {
        this.mockModusActive = mockModusActive;
    }

    public abstract void cleanUp();

    public abstract void setI18n();

    private @Inject @CDILogger Logger logger;

    private Boolean initCompleteCDI = false;
    private Boolean initCompleteFX = false;

    public CompletableFuture supplyAsync;

    @Override
    public final void initInstance(){
        final CachedThreadPoolSingleton instance = CachedThreadPoolSingleton.getInstance();
        supplyAsync = CompletableFuture.supplyAsync(task, instance.cachedThreadPool);
        if (logger.isDebugEnabled()) supplyAsync.thenAccept(logger::debug);  //logger
    }

    public final Supplier task = ()-> {
//        Warten bis alle true
        while(! (initCompleteCDI && initCompleteFX) ){
            try {
                //evtl loggen
                if (logger.isDebugEnabled()) {
                    logger.debug("initCompleteCDI = " + initCompleteCDI);
                    logger.debug("initCompleteFX = " + initCompleteFX);
                    logger.debug("getClass().getName() = " + getClass().getName());
                }
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return e.toString();
            }
        }

        if (logger.isInfoEnabled()) {
            logger.info("initBusinessLogic() => called now");
        }
        final boolean fxApplicationThread = Platform.isFxApplicationThread();
        if ( ! fxApplicationThread){
            Platform.runLater(this::initBusinessLogic);
        } else {
            initBusinessLogic();
        }


        if (logger.isInfoEnabled()) {
            logger.info("initBusinessLogic() => done now");
        }
        return DONE;
    };

    @PostConstruct
    public final void postconstruct(){
        if (logger.isDebugEnabled()) {
            logger.debug("PostConstruct mockModusActive == " + mockModusActive);
        }
        cdiPostConstruct();
        initCompleteCDI = true;
        if (logger.isDebugEnabled()) {
            logger.debug("postconstruct ready " + getClass().getName());
        }
    }

    public abstract void cdiPostConstruct();

    @Override
    public final void initialize(URL url, ResourceBundle resourceBundle) {
        if (logger.isDebugEnabled()) {
            logger.debug("initialize mockModusActive== " + mockModusActive);
        }
        initializeFX(url, resourceBundle);
        initCompleteFX = true;
        if (logger.isDebugEnabled()) {
            logger.debug("initialize ready " + getClass().getName());
        }
    }


    protected abstract void initializeFX(URL url, ResourceBundle resourceBundle);
    /**
     * wird nach der init von CDI und JavaFX aufgerufen,
     * egal in welcher Reihenfolge die init durchlaufen wird.
     *
     * ein blockierender method call
     *
     */
    public abstract void initBusinessLogic();
}