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