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