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