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