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