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