/* * Copyright (C) 2013,2014 The Cat Hive Developers. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cathive.fx.cdi; import javafx.application.Application; import javafx.application.Platform; import javax.enterprise.context.Dependent; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.event.Observes; import javax.enterprise.inject.Any; import javax.enterprise.inject.Default; import javax.enterprise.inject.spi.*; import javax.enterprise.util.AnnotationLiteral; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.logging.Logger; /** * This JavaFX extension makes sure that sub-classes of {@link com.cathive.fx.cdi.CdiApplication} can be * injected (using the {@link javax.inject.Inject @javax.inject.Inject} annotation). * @author Benjamin P. Jung */ @SuppressWarnings({ "UnusedDeclaration", "CdiManagedBeanInconsistencyInspection" }) class JavaFXExtension implements Extension { /** Logger for this instance. */ private final Logger logger = Logger.getLogger(this.getClass().getName()); /** * The JavaFX application instance to be provided by the CDI BeanManager. * <p>It is crucial that this field is populated (using {@link #setJavaFxApplication(CdiApplication)} * prior to initializing the CDI context.</p> */ static ThreadLocal<CdiApplication> JAVA_FX_APPLICATION = new ThreadLocal<>(); private CdiApplicationBean<CdiApplication> JAVA_FX_APPLICATION_BEAN; /** * Sets the JavaFX application instance to be provided by the CDI BeanManager. * @param javaFxApplication * The JavaFX application instance to be provided by the CDI BeanManager. */ public static void setJavaFxApplication(final CdiApplication javaFxApplication) { final CountDownLatch latch = new CountDownLatch(1); Platform.runLater(() -> { JAVA_FX_APPLICATION.set(javaFxApplication); latch.countDown(); }); if (!Platform.isFxApplicationThread()) { try { latch.await(); } catch (InterruptedException e) { throw new IllegalStateException(e); } } } public static <T extends CdiApplication> T getJavaFxApplication() { final ValueLatch<CdiApplication> latch = new ValueLatch<>(1); Platform.runLater(() -> { latch.setValue(JAVA_FX_APPLICATION.get()); latch.countDown(); }); if (!Platform.isFxApplicationThread()) { try { latch.await(); } catch (InterruptedException e) { throw new IllegalStateException(e); } } return (T) latch.getValue(); } // ---- ==== CDI LIFECYCLE EVENTS ==== ----------------------------------------------------------------------------- void beforeBeanDiscovery(@Observes final BeforeBeanDiscovery beforeBeanDiscovery, final BeanManager beanManager) { if (getJavaFxApplication() == null) { throw new IllegalStateException("JavaFX application was not provided. Did you call FxCdiExtension.setJavaFxApplication(..)?"); } } void processAnnotatedType(@Observes final ProcessAnnotatedType<? extends Application> pat, final BeanManager beanManager) { final Class<? extends Application> javaClass = pat.getAnnotatedType().getJavaClass(); if (javaClass.equals(getJavaFxApplication().getClass())) { // We veto any sub-classes of CdiApplication to make sure, that the right bean // will be pulled into the dependency graph when using @Inject annotations. pat.veto(); } } <X> void processInjectionTarget(@Observes final ProcessInjectionTarget<X> pit, final BeanManager beanManager) { final Class<X> javaClass = pit.getAnnotatedType().getJavaClass(); if (javaClass.equals(getJavaFxApplication().getClass())) { } } <T, X> void processInjectionPoint(@Observes final ProcessInjectionPoint<T, X> pip, final BeanManager beanManager) { } void afterBeanDiscovery(@Observes final AfterBeanDiscovery abd, final BeanManager beanManager) { assert JAVA_FX_APPLICATION_BEAN == null; final AnnotatedType<CdiApplication> annotatedType = (AnnotatedType<CdiApplication>) beanManager.createAnnotatedType(getJavaFxApplication().getClass()); final BeanAttributes<CdiApplication> beanAttributes = beanManager.createBeanAttributes(annotatedType); final InjectionTarget<CdiApplication> injectionTarget = beanManager.createInjectionTarget(annotatedType); // Obtains the one (and only) application instance to ever be used. final CdiApplication instance = getJavaFxApplication(); // Constructs the bean used to provide instances of the JavaFX application instance. JAVA_FX_APPLICATION_BEAN = new CdiApplicationBean(instance, beanAttributes, injectionTarget); abd.addBean(JAVA_FX_APPLICATION_BEAN); } void afterDeploymentValidation(@Observes final AfterDeploymentValidation adv, final BeanManager beanManager) { final CreationalContext<CdiApplication> creationalContext = beanManager.createCreationalContext(JAVA_FX_APPLICATION_BEAN); JAVA_FX_APPLICATION_BEAN.injectionTarget.inject(getJavaFxApplication(), creationalContext); } void beforeShutdown(@Observes final BeforeShutdown beforeShutdown, final BeanManager beanManager) { this.JAVA_FX_APPLICATION_BEAN = null; } /** * A simple wrapper class that helps us to get things up and running. * @author Benjamin P. Jung */ static class CdiApplicationBean<T extends CdiApplication> implements Bean<T>, PassivationCapable { /** The scope to be used for our JavaFX application instance. */ static final Class<? extends Annotation> SCOPE = Dependent.class; final T instance; final Set<Annotation> qualifiers; final BeanAttributes<T> beanAttributes; final InjectionTarget<T> injectionTarget; public CdiApplicationBean(final T instance, final BeanAttributes<T> beanAttributes, final InjectionTarget<T> injectionTarget) { super(); this.instance = instance; this.qualifiers = new HashSet<>(); this.qualifiers.add(new AnnotationLiteral<Any>() {}); this.qualifiers.add(new AnnotationLiteral<Default>() {}); this.beanAttributes = beanAttributes; this.injectionTarget = injectionTarget; } @Override public Class<?> getBeanClass() { return this.instance.getClass(); } @Override public Set<InjectionPoint> getInjectionPoints() { return this.injectionTarget.getInjectionPoints(); } @Override public boolean isNullable() { return false; } @Override public Set<Type> getTypes() { return this.beanAttributes.getTypes(); } @Override public Set<Annotation> getQualifiers() { return this.qualifiers; } @Override public Class<? extends Annotation> getScope() { return SCOPE; } @Override public String getName() { return this.beanAttributes.getName(); } @Override public Set<Class<? extends Annotation>> getStereotypes() { return this.beanAttributes.getStereotypes(); } @Override public boolean isAlternative() { return this.beanAttributes.isAlternative(); } @Override public T create(CreationalContext<T> creationalContext) { return this.instance; } @Override public void destroy(T instance, CreationalContext<T> creationalContext) { // TODO Should we call instance.stop() here? I actually don't think so... } @Override public String getId() { return this.instance.getClass().getName(); } } /** * A simple countdown latch that can carry a value * @param <T> * Type of the value to be carried. */ private static class ValueLatch<T> extends CountDownLatch { /** * Constructs a {@code CountDownLatch} initialized with the given count. * * @param count the number of times {@link #countDown} must be invoked * before threads can pass through {@link #await} * @throws IllegalArgumentException if {@code count} is negative */ public ValueLatch(final int count) { super(count); } private T value; public T getValue() { return this.value; } public void setValue(final T value) { this.value = value; } } }