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