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