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