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