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