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