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