AbstractGriffonApplet.java
001 /*
002  * Copyright 2008-2016 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.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 null");
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.STARTUPreturn;
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.SHUTDOWNreturn 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() 0);
329             getEventRouter().addEventListener(ApplicationEvent.SHUTDOWN_START.getName()new RunnableWithArgs() {
330                 @Override
331                 public void run(@Nullable Object... args) {
332                     latch.countDown();
333                 }
334             });
335             event(ApplicationEvent.SHUTDOWN_START, asList(this));
336             try {
337                 latch.await();
338             catch (InterruptedException e) {
339                 // ignore
340             }
341         }
342 
343         // stage 2 - alert all shutdown handlers
344         log.debug("Shutdown stage 2: notify all shutdown handlers");
345         synchronized (shutdownLock) {
346             for (ShutdownHandler handler : shutdownHandlers) {
347                 handler.onShutdown(this);
348             }
349         }
350 
351         // stage 3 - destroy all mvc groups
352         log.debug("Shutdown stage 3: destroy all MVC groups");
353         List<String> mvcIds = new ArrayList<>();
354         mvcIds.addAll(getMvcGroupManager().getGroups().keySet());
355         for (String id : mvcIds) {
356             getMvcGroupManager().destroyMVCGroup(id);
357         }
358 
359         // stage 4 - call shutdown script
360         log.debug("Shutdown stage 4: execute Shutdown script");
361         getApplicationConfigurer().runLifecycleHandler(Lifecycle.SHUTDOWN);
362 
363         injector.getInstance(ExecutorServiceManager.class).shutdownAll();
364         injector.close();
365 
366         return true;
367     }
368 
369     @SuppressWarnings("unchecked")
370     public void startup() {
371         if (getPhase() != ApplicationPhase.INITIALIZEreturn;
372 
373         setPhase(ApplicationPhase.STARTUP);
374         event(ApplicationEvent.STARTUP_START, asList(this));
375 
376         Object startupGroups = getConfiguration().get("application.startupGroups"null);
377         if (startupGroups instanceof List) {
378             log.info("Initializing all startup groups: {}", startupGroups);
379 
380             for (String groupName : (List<String>startupGroups) {
381                 getMvcGroupManager().createMVC(groupName.trim());
382             }
383         else if (startupGroups != null && startupGroups.getClass().isArray()) {
384             Object[] groups = (Object[]) startupGroups;
385             log.info("Initializing all startup groups: {}", Arrays.toString(groups));
386 
387             for (Object groupName : groups) {
388                 getMvcGroupManager().createMVC(String.valueOf(groupName).trim());
389             }
390         else if (startupGroups != null && startupGroups instanceof String) {
391             String[] groups = ((StringstartupGroups).split(",");
392             log.info("Initializing all startup groups: {}", Arrays.toString(groups));
393 
394             for (String groupName : groups) {
395                 getMvcGroupManager().createMVC(groupName.trim());
396             }
397         }
398 
399         for (Map.Entry<String, GriffonAddon> e : getAddonManager().getAddons().entrySet()) {
400             List<String> groups = e.getValue().getStartupGroups();
401             if (groups.isEmpty()) {
402                 continue;
403             }
404             log.info("Initializing all {} startup groups: {}", e.getKey(), groups);
405             Map<String, Map<String, Object>> mvcGroups = e.getValue().getMvcGroups();
406             for (String groupName : groups) {
407                 if (mvcGroups.containsKey(groupName)) {
408                     getMvcGroupManager().createMVC(groupName.trim());
409                 }
410             }
411         }
412 
413         getApplicationConfigurer().runLifecycleHandler(Lifecycle.STARTUP);
414 
415         event(ApplicationEvent.STARTUP_END, asList(this));
416     }
417 
418     protected void event(@Nonnull ApplicationEvent event, @Nullable List<?> args) {
419         getEventRouter().publishEvent(event.getName(), args);
420     }
421 }