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