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