AbstractJavaFXGriffonApplication.java
001 /*
002  * Copyright 2008-2017 the original author or authors.
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  */
016 package griffon.javafx;
017 
018 import griffon.core.ApplicationClassLoader;
019 import griffon.core.ApplicationConfigurer;
020 import griffon.core.ApplicationEvent;
021 import griffon.core.Configuration;
022 import griffon.core.Context;
023 import griffon.core.ExecutorServiceManager;
024 import griffon.core.GriffonApplication;
025 import griffon.core.ShutdownHandler;
026 import griffon.core.addon.AddonManager;
027 import griffon.core.addon.GriffonAddon;
028 import griffon.core.artifact.ArtifactManager;
029 import griffon.core.configuration.ConfigurationManager;
030 import griffon.core.controller.ActionManager;
031 import griffon.core.env.ApplicationPhase;
032 import griffon.core.env.Lifecycle;
033 import griffon.core.event.EventRouter;
034 import griffon.core.i18n.MessageSource;
035 import griffon.core.injection.Injector;
036 import griffon.core.mvc.MVCGroupManager;
037 import griffon.core.resources.ResourceHandler;
038 import griffon.core.resources.ResourceInjector;
039 import griffon.core.resources.ResourceResolver;
040 import griffon.core.threading.UIThreadManager;
041 import griffon.core.view.WindowManager;
042 import javafx.application.Application;
043 import javafx.application.Platform;
044 import javafx.stage.Stage;
045 import org.codehaus.griffon.runtime.core.MVCGroupExceptionHandler;
046 import org.slf4j.Logger;
047 import org.slf4j.LoggerFactory;
048 
049 import javax.annotation.Nonnull;
050 import javax.annotation.Nullable;
051 import java.beans.PropertyChangeEvent;
052 import java.beans.PropertyChangeListener;
053 import java.beans.PropertyChangeSupport;
054 import java.util.ArrayList;
055 import java.util.Arrays;
056 import java.util.List;
057 import java.util.Locale;
058 import java.util.Map;
059 import java.util.concurrent.CountDownLatch;
060 
061 import static griffon.util.AnnotationUtils.named;
062 import static griffon.util.GriffonApplicationUtils.parseLocale;
063 import static griffon.util.GriffonNameUtils.requireNonBlank;
064 import static java.util.Arrays.asList;
065 import static java.util.Objects.requireNonNull;
066 
067 /**
068  * Base implementation of {@code GriffonApplication} that runs in applet mode.
069  *
070  @author Andres Almiray
071  @since 2.0.0
072  */
073 public abstract class AbstractJavaFXGriffonApplication extends Application implements GriffonApplication {
074     public static final String[] EMPTY_ARGS = new String[0];
075     private static final String ERROR_SHUTDOWN_HANDLER_NULL = "Argument 'shutdownHandler' must not be null";
076     protected final Object[] lock = new Object[0];
077     protected final PropertyChangeSupport pcs;
078     private final List<ShutdownHandler> shutdownHandlers = new ArrayList<>();
079     private final Object shutdownLock = new Object();
080     private final Logger log;
081     private Locale locale = Locale.getDefault();
082     private ApplicationPhase phase = ApplicationPhase.INITIALIZE;
083     private String[] startupArgs;
084     private Injector<?> injector;
085 
086     public AbstractJavaFXGriffonApplication() {
087         this(EMPTY_ARGS);
088     }
089 
090     public AbstractJavaFXGriffonApplication(@Nonnull String[] args) {
091         requireNonNull(args, "Argument 'args' must not be null");
092         pcs = new PropertyChangeSupport(this);
093         startupArgs = Arrays.copyOf(args, args.length);
094         log = LoggerFactory.getLogger(getClass());
095     }
096 
097     // ------------------------------------------------------
098 
099     @Override
100     public void start(Stage stagethrows Exception {
101         stage.setOnHidden(t -> shutdown());
102     }
103 
104     @Override
105     public void stop() throws Exception {
106         shutdown();
107     }
108 
109     public void addPropertyChangeListener(@Nullable PropertyChangeListener listener) {
110         pcs.addPropertyChangeListener(listener);
111     }
112 
113     public void addPropertyChangeListener(@Nullable String propertyName, @Nullable PropertyChangeListener listener) {
114         pcs.addPropertyChangeListener(propertyName, listener);
115     }
116 
117     public void removePropertyChangeListener(@Nullable PropertyChangeListener listener) {
118         pcs.removePropertyChangeListener(listener);
119     }
120 
121     public void removePropertyChangeListener(@Nullable String propertyName, @Nullable PropertyChangeListener listener) {
122         pcs.removePropertyChangeListener(propertyName, listener);
123     }
124 
125     @Nonnull
126     public PropertyChangeListener[] getPropertyChangeListeners() {
127         return pcs.getPropertyChangeListeners();
128     }
129 
130     @Nonnull
131     public PropertyChangeListener[] getPropertyChangeListeners(@Nullable String propertyName) {
132         return pcs.getPropertyChangeListeners(propertyName);
133     }
134 
135     protected void firePropertyChange(@Nonnull PropertyChangeEvent event) {
136         pcs.firePropertyChange(requireNonNull(event, "Argument 'event' must not be null"));
137     }
138 
139     protected void firePropertyChange(@Nonnull String propertyName, @Nullable Object oldValue, @Nullable Object newValue) {
140         pcs.firePropertyChange(requireNonBlank(propertyName, "Argument 'propertyName' must not be blank"), oldValue, newValue);
141     }
142 
143     // ------------------------------------------------------
144 
145     @Nonnull
146     public Locale getLocale() {
147         return locale;
148     }
149 
150     public void setLocale(@Nonnull Locale locale) {
151         Locale oldValue = this.locale;
152         this.locale = locale;
153         Locale.setDefault(locale);
154         firePropertyChange(PROPERTY_LOCALE, oldValue, locale);
155     }
156 
157     @Nonnull
158     public String[] getStartupArgs() {
159         return startupArgs;
160     }
161 
162     @Nonnull
163     public Logger getLog() {
164         return log;
165     }
166 
167     public void setLocaleAsString(@Nullable String locale) {
168         setLocale(parseLocale(locale));
169     }
170 
171     public void addShutdownHandler(@Nonnull ShutdownHandler handler) {
172         requireNonNull(handler, ERROR_SHUTDOWN_HANDLER_NULL);
173         if (!shutdownHandlers.contains(handler)) shutdownHandlers.add(handler);
174     }
175 
176     public void removeShutdownHandler(@Nonnull ShutdownHandler handler) {
177         requireNonNull(handler, ERROR_SHUTDOWN_HANDLER_NULL);
178         shutdownHandlers.remove(handler);
179     }
180 
181     @Nonnull
182     public ApplicationPhase getPhase() {
183         synchronized (lock) {
184             return this.phase;
185         }
186     }
187 
188     protected void setPhase(@Nonnull ApplicationPhase phase) {
189         requireNonNull(phase, "Argument 'phase' must not be null");
190         synchronized (lock) {
191             firePropertyChange(PROPERTY_PHASE, this.phase, this.phase = phase);
192         }
193     }
194 
195     @Nonnull
196     @Override
197     public ApplicationClassLoader getApplicationClassLoader() {
198         return injector.getInstance(ApplicationClassLoader.class);
199     }
200 
201     @Nonnull
202     @Override
203     public Context getContext() {
204         return injector.getInstance(Context.class, named("applicationContext"));
205     }
206 
207     @Nonnull
208     @Override
209     public Configuration getConfiguration() {
210         return getConfigurationManager().getConfiguration();
211     }
212 
213     @Nonnull
214     @Override
215     public ConfigurationManager getConfigurationManager() {
216         return injector.getInstance(ConfigurationManager.class);
217     }
218 
219     @Nonnull
220     @Override
221     public UIThreadManager getUIThreadManager() {
222         return injector.getInstance(UIThreadManager.class);
223     }
224 
225     @Nonnull
226     @Override
227     public EventRouter getEventRouter() {
228         return injector.getInstance(EventRouter.class, named("applicationEventRouter"));
229     }
230 
231     @Nonnull
232     @Override
233     public ArtifactManager getArtifactManager() {
234         return injector.getInstance(ArtifactManager.class);
235     }
236 
237     @Nonnull
238     @Override
239     public ActionManager getActionManager() {
240         return injector.getInstance(ActionManager.class);
241     }
242 
243     @Nonnull
244     @Override
245     public AddonManager getAddonManager() {
246         return injector.getInstance(AddonManager.class);
247     }
248 
249     @Nonnull
250     @Override
251     public MVCGroupManager getMvcGroupManager() {
252         return injector.getInstance(MVCGroupManager.class);
253     }
254 
255     @Nonnull
256     @Override
257     public MessageSource getMessageSource() {
258         return injector.getInstance(MessageSource.class, named("applicationMessageSource"));
259     }
260 
261     @Nonnull
262     @Override
263     public ResourceResolver getResourceResolver() {
264         return injector.getInstance(ResourceResolver.class, named("applicationResourceResolver"));
265     }
266 
267     @Nonnull
268     @Override
269     public ResourceHandler getResourceHandler() {
270         return injector.getInstance(ResourceHandler.class);
271     }
272 
273     @Nonnull
274     @Override
275     public ResourceInjector getResourceInjector() {
276         return injector.getInstance(ResourceInjector.class, named("applicationResourceInjector"));
277     }
278 
279     @Nonnull
280     @Override
281     public Injector<?> getInjector() {
282         return injector;
283     }
284 
285     public void setInjector(@Nonnull Injector<?> injector) {
286         this.injector = requireNonNull(injector, "Argument 'injector' must not be null");
287         this.injector.injectMembers(this);
288         addShutdownHandler(getWindowManager());
289         MVCGroupExceptionHandler.registerWith(this);
290     }
291 
292     @Nonnull
293     @Override
294     @SuppressWarnings("unchecked")
295     public <W> WindowManager<W> getWindowManager() {
296         return injector.getInstance(WindowManager.class);
297     }
298 
299     protected ApplicationConfigurer getApplicationConfigurer() {
300         return injector.getInstance(ApplicationConfigurer.class);
301     }
302 
303     public void initialize() {
304         if (getPhase() == ApplicationPhase.INITIALIZE) {
305             Parameters parameters = getParameters();
306             if (parameters != null && parameters.getRaw().size() 0) {
307                 int length = parameters.getRaw().size();
308                 startupArgs = new String[length];
309                 System.arraycopy(parameters.getRaw().toArray()0, startupArgs, 0, length);
310             }
311 
312             getApplicationConfigurer().init();
313         }
314     }
315 
316     public void ready() {
317         if (getPhase() != ApplicationPhase.STARTUPreturn;
318 
319         showStartingWindow();
320 
321         setPhase(ApplicationPhase.READY);
322         event(ApplicationEvent.READY_START, asList(this));
323 
324         getApplicationConfigurer().runLifecycleHandler(Lifecycle.READY);
325         event(ApplicationEvent.READY_END, asList(this));
326 
327         setPhase(ApplicationPhase.MAIN);
328     }
329 
330     protected void showStartingWindow() {
331         Object startingWindow = getWindowManager().getStartingWindow();
332         if (startingWindow != null) {
333             getWindowManager().show(startingWindow);
334         }
335     }
336 
337     public boolean canShutdown() {
338         event(ApplicationEvent.SHUTDOWN_REQUESTED, asList(this));
339         synchronized (shutdownLock) {
340             for (ShutdownHandler handler : shutdownHandlers) {
341                 if (!handler.canShutdown(this)) {
342                     event(ApplicationEvent.SHUTDOWN_ABORTED, asList(this));
343                     if (log.isDebugEnabled()) {
344                         try {
345                             log.debug("Shutdown aborted by " + handler);
346                         catch (UnsupportedOperationException uoe) {
347                             log.debug("Shutdown aborted by a handler");
348                         }
349                     }
350                     return false;
351                 }
352             }
353         }
354         return true;
355     }
356 
357     public boolean shutdown() {
358         boolean implicitExit = Platform.isImplicitExit();
359         Platform.setImplicitExit(false);
360         if (!doShutdown()) {
361             Platform.setImplicitExit(implicitExit);
362             return false;
363         }
364         return true;
365     }
366 
367     protected boolean doShutdown() {
368         // avoids reentrant calls to shutdown()
369         // once permission to quit has been granted
370         if (getPhase() == ApplicationPhase.SHUTDOWNreturn false;
371 
372         if (!canShutdown()) return false;
373         log.info("Shutdown is in process");
374 
375         // signal that shutdown is in process
376         setPhase(ApplicationPhase.SHUTDOWN);
377 
378         // stage 1 - alert all app event handlers
379         // wait for all handlers to complete before proceeding
380         // with stage #2 if and only if the current thread is
381         // the ui thread
382         log.debug("Shutdown stage 1: notify all event listeners");
383         if (getEventRouter().isEventPublishingEnabled()) {
384             final CountDownLatch latch = new CountDownLatch(getUIThreadManager().isUIThread() 0);
385             getEventRouter().addEventListener(ApplicationEvent.SHUTDOWN_START.getName()(Object... args-> latch.countDown());
386             event(ApplicationEvent.SHUTDOWN_START, asList(this));
387             try {
388                 latch.await();
389             catch (InterruptedException e) {
390                 // ignore
391             }
392         }
393 
394         // stage 2 - alert all shutdown handlers
395         log.debug("Shutdown stage 2: notify all shutdown handlers");
396         synchronized (shutdownLock) {
397             for (ShutdownHandler handler : shutdownHandlers) {
398                 handler.onShutdown(this);
399             }
400         }
401 
402         // stage 3 - destroy all mvc groups
403         log.debug("Shutdown stage 3: destroy all MVC groups");
404         List<String> mvcIds = new ArrayList<>();
405         mvcIds.addAll(getMvcGroupManager().getGroups().keySet());
406         for (String id : mvcIds) {
407             getMvcGroupManager().destroyMVCGroup(id);
408         }
409 
410         // stage 4 - call shutdown script
411         log.debug("Shutdown stage 4: execute Shutdown script");
412         getApplicationConfigurer().runLifecycleHandler(Lifecycle.SHUTDOWN);
413 
414         injector.getInstance(ExecutorServiceManager.class).shutdownAll();
415         injector.close();
416 
417         return true;
418     }
419 
420     @SuppressWarnings("unchecked")
421     public void startup() {
422         if (getPhase() != ApplicationPhase.INITIALIZEreturn;
423 
424         setPhase(ApplicationPhase.STARTUP);
425         event(ApplicationEvent.STARTUP_START, asList(this));
426 
427         Object startupGroups = getConfiguration().get("application.startupGroups"null);
428         if (startupGroups instanceof List) {
429             log.info("Initializing all startup groups: {}", startupGroups);
430 
431             for (String groupName : (List<String>startupGroups) {
432                 getMvcGroupManager().createMVC(groupName.trim());
433             }
434         else if (startupGroups != null && startupGroups.getClass().isArray()) {
435             Object[] groups = (Object[]) startupGroups;
436             log.info("Initializing all startup groups: {}", Arrays.toString(groups));
437 
438             for (Object groupName : groups) {
439                 getMvcGroupManager().createMVC(String.valueOf(groupName).trim());
440             }
441         else if (startupGroups != null && startupGroups instanceof CharSequence) {
442             String[] groups = (String.valueOf(startupGroups)).split(",");
443             log.info("Initializing all startup groups: {}", Arrays.toString(groups));
444 
445             for (String groupName : groups) {
446                 getMvcGroupManager().createMVC(groupName.trim());
447             }
448         }
449 
450         for (Map.Entry<String, GriffonAddon> e : getAddonManager().getAddons().entrySet()) {
451             List<String> groups = e.getValue().getStartupGroups();
452             if (groups.isEmpty()) {
453                 continue;
454             }
455             log.info("Initializing all {} startup groups: {}", e.getKey(), groups);
456             Map<String, Map<String, Object>> mvcGroups = e.getValue().getMvcGroups();
457             for (String groupName : groups) {
458                 if (mvcGroups.containsKey(groupName)) {
459                     getMvcGroupManager().createMVC(groupName.trim());
460                 }
461             }
462         }
463 
464         getApplicationConfigurer().runLifecycleHandler(Lifecycle.STARTUP);
465 
466         event(ApplicationEvent.STARTUP_END, asList(this));
467     }
468 
469     protected void event(@Nonnull ApplicationEvent event, @Nullable List<?> args) {
470         getEventRouter().publishEvent(event.getName(), args);
471     }
472 }