AbstractGriffonApplet.java
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.STARTUPreturn;
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.SHUTDOWNreturn 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() 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.INITIALIZEreturn;
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 }