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 stage) throws 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.STARTUP) return;
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.SHUTDOWN) return 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() ? 1 : 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.INITIALIZE) return;
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 }
|