001/*
002 * Copyright 2019-2021 M. Sean Gilligan.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package app.supernaut.fx.micronaut;
017
018import app.supernaut.fx.ApplicationDelegate;
019import app.supernaut.fx.fxml.FxmlLoaderFactory;
020import app.supernaut.fx.micronaut.fxml.MicronautFxmlLoaderFactory;
021import app.supernaut.fx.services.FxBrowserService;
022import app.supernaut.fx.test.NoopBackgroundApp;
023import io.micronaut.context.ApplicationContext;
024import io.micronaut.context.BeanContext;
025import io.micronaut.context.env.Environment;
026import javafx.application.Application;
027import javafx.application.HostServices;
028import app.supernaut.BackgroundApp;
029import app.supernaut.services.BrowserService;
030import app.supernaut.fx.FxLauncherAbstract;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034/**
035 * A launcher that uses <a href="https://micronaut.io">Micronaut@ framework</a> to instantiate and Dependency Inject
036 * the foreground and background applications.
037 */
038public class MicronautFxLauncher extends FxLauncherAbstract {
039    private static final Logger log = LoggerFactory.getLogger(FxLauncherAbstract.class);
040
041    /**
042     * Default constructor that initializes the background app on its own thread.
043     */
044    public MicronautFxLauncher() {
045        this(true);
046    }
047
048    /**
049     *
050     * @param initializeBackgroundAppOnNewThread If true, initializes {@code appFactorySupplier} and
051     *        {@code BackgroundApp} on new thread, if false start them on calling thread (typically the main thread)
052     */
053    public MicronautFxLauncher(boolean initializeBackgroundAppOnNewThread) {
054        super(() -> new MicronautAppFactory(false), initializeBackgroundAppOnNewThread);
055    }
056
057    /**
058     *
059     * @param initializeBackgroundAppOnNewThread If true, initializes {@code appFactorySupplier} and
060     *        {@code BackgroundApp} on new thread, if false start them on calling thread (typically the main thread)
061     * @param useApplicationContext If {@code true} creates and uses an {@link ApplicationContext},
062     *                             if {@code false} creates and uses a {@link BeanContext}
063     */
064    public MicronautFxLauncher(boolean initializeBackgroundAppOnNewThread,
065                               boolean useApplicationContext) {
066        super(() -> new MicronautAppFactory(useApplicationContext), initializeBackgroundAppOnNewThread);
067    }
068
069    /**
070     * {@inheritDoc}
071     */
072    @Override
073    public String name() {
074        return "micronaut";
075    }
076
077    /**
078     * Implement of AppFactory using either a Micronaut {@link BeanContext} or {@link ApplicationContext}
079     */
080    public static class MicronautAppFactory implements AppFactory {
081        private final BeanContext context;
082
083        /**
084         * Constructor for Micronaut implementation of AppFactory
085         * @param useApplicationContext create {@link ApplicationContext} if true, {@link BeanContext} if false
086         */
087        public MicronautAppFactory(boolean useApplicationContext) {
088            if (useApplicationContext) {
089                log.info("Creating Micronaut ApplicationContext");
090                this.context = ApplicationContext.builder(Environment.CLI).build();
091            } else {
092                log.info("Creating Micronaut BeanContext");
093                this.context = BeanContext.build();
094            }
095
096            log.info("Starting context");
097            context.start();
098        }
099
100        /**
101         * {@inheritDoc}
102         */
103        @Override
104        public BackgroundApp createBackgroundApp(Class<? extends BackgroundApp> backgroundAppClass) {
105            if (backgroundAppClass.equals(NoopBackgroundApp.class)) {
106                // Special case for NoopBackgroundApp which is not an (annotated) Micronaut Bean
107                return new NoopBackgroundApp();
108            } else {
109                return context.getBean(backgroundAppClass);
110            }
111        }
112
113        /**
114         * {@inheritDoc}
115         */
116        @Override
117        public ApplicationDelegate createAppDelegate(Class<? extends ApplicationDelegate> appDelegateClass, Application proxyApplication) {
118            log.info("getForegroundApp()");
119            initializeBeanContext(context, proxyApplication);
120            return context.getBean(appDelegateClass);
121        }
122
123        /**
124         * Subclass {@link MicronautAppFactory} and override this method to customize your {@link BeanContext}.
125         *
126         * @param context The Micronaut BeanContext to initialize
127         * @param proxyApplication The proxy implementation instance of {@link Application}
128         */
129        protected void initializeBeanContext(BeanContext context, Application proxyApplication) {
130            log.info("initializeBeanContext()");
131            // An app that wants access to the Application object can have it injected.
132            context.registerSingleton(Application.class, proxyApplication);
133
134            // An app that needs HostServices can have it injected. For opening URLs in browsers
135            // the BrowserService interface is preferred.
136            context.registerSingleton(HostServices.class, proxyApplication.getHostServices());
137            context.registerSingleton(BrowserService.class, new FxBrowserService(proxyApplication.getHostServices()));
138
139            // TODO: Make this dependency on FXML optional
140            context.registerSingleton(FxmlLoaderFactory.class, new MicronautFxmlLoaderFactory(context));
141        }
142    }
143}