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