AbstractGriffonApplication.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 org.codehaus.griffon.runtime.core;
019 
020 import griffon.core.ApplicationBootstrapper;
021 import griffon.core.ApplicationClassLoader;
022 import griffon.core.ApplicationConfigurer;
023 import griffon.core.ApplicationEvent;
024 import griffon.core.Configuration;
025 import griffon.core.Context;
026 import griffon.core.ExecutorServiceManager;
027 import griffon.core.GriffonApplication;
028 import griffon.core.GriffonExceptionHandler;
029 import griffon.core.RunnableWithArgs;
030 import griffon.core.ShutdownHandler;
031 import griffon.core.addon.AddonManager;
032 import griffon.core.addon.GriffonAddon;
033 import griffon.core.artifact.ArtifactManager;
034 import griffon.core.configuration.ConfigurationManager;
035 import griffon.core.controller.ActionManager;
036 import griffon.core.env.ApplicationPhase;
037 import griffon.core.env.Lifecycle;
038 import griffon.core.event.EventRouter;
039 import griffon.core.i18n.MessageSource;
040 import griffon.core.injection.Injector;
041 import griffon.core.mvc.MVCGroupManager;
042 import griffon.core.resources.ResourceHandler;
043 import griffon.core.resources.ResourceInjector;
044 import griffon.core.resources.ResourceResolver;
045 import griffon.core.threading.UIThreadManager;
046 import griffon.core.view.WindowManager;
047 import org.slf4j.Logger;
048 import org.slf4j.LoggerFactory;
049 
050 import javax.annotation.Nonnull;
051 import javax.annotation.Nullable;
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  * Implements the basics for a skeleton GriffonApplication.<p>
066  *
067  @author Danno Ferrin
068  @author Andres Almiray
069  */
070 public abstract class AbstractGriffonApplication extends AbstractObservable implements GriffonApplication {
071     public static final String[] EMPTY_ARGS = new String[0];
072     private static final String ERROR_SHUTDOWN_HANDLER_NULL = "Argument 'shutdownHandler' must not be null";
073     private static final Class<?>[] CTOR_ARGS = new Class<?>[]{String[].class};
074     protected final Object[] lock = new Object[0];
075     private final List<ShutdownHandler> shutdownHandlers = new ArrayList<>();
076     private final String[] startupArgs;
077     private final Object shutdownLock = new Object();
078     private final Logger log;
079     private Locale locale = Locale.getDefault();
080     private ApplicationPhase phase = ApplicationPhase.INITIALIZE;
081     private Injector<?> injector;
082 
083     public AbstractGriffonApplication() {
084         this(EMPTY_ARGS);
085     }
086 
087     public AbstractGriffonApplication(@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     @Nonnull
094     public static GriffonApplication run(@Nonnull Class<? extends GriffonApplication> applicationClass, @Nonnull String[] argsthrows Exception {
095         GriffonExceptionHandler.registerExceptionHandler();
096 
097         GriffonApplication application = applicationClass.getDeclaredConstructor(CTOR_ARGS).newInstance(new Object[]{args});
098         ApplicationBootstrapper bootstrapper = new DefaultApplicationBootstrapper(application);
099         bootstrapper.bootstrap();
100         bootstrapper.run();
101 
102         return application;
103     }
104 
105     @Nonnull
106     public Locale getLocale() {
107         return locale;
108     }
109 
110     public void setLocale(@Nonnull Locale locale) {
111         Locale oldValue = this.locale;
112         this.locale = locale;
113         Locale.setDefault(locale);
114         firePropertyChange(PROPERTY_LOCALE, oldValue, locale);
115     }
116 
117     @Nonnull
118     public String[] getStartupArgs() {
119         return startupArgs;
120     }
121 
122     @Nonnull
123     public Logger getLog() {
124         return log;
125     }
126 
127     public void setLocaleAsString(@Nullable String locale) {
128         setLocale(parseLocale(locale));
129     }
130 
131     public void addShutdownHandler(@Nonnull ShutdownHandler handler) {
132         requireNonNull(handler, ERROR_SHUTDOWN_HANDLER_NULL);
133         if (!shutdownHandlers.contains(handler)) shutdownHandlers.add(handler);
134     }
135 
136     public void removeShutdownHandler(@Nonnull ShutdownHandler handler) {
137         requireNonNull(handler, ERROR_SHUTDOWN_HANDLER_NULL);
138         shutdownHandlers.remove(handler);
139     }
140 
141     @Nonnull
142     public ApplicationPhase getPhase() {
143         synchronized (lock) {
144             return this.phase;
145         }
146     }
147 
148     protected void setPhase(@Nonnull ApplicationPhase phase) {
149         requireNonNull(phase, "Argument 'phase' must not be null");
150         synchronized (lock) {
151             firePropertyChange(PROPERTY_PHASE, this.phase, this.phase = phase);
152         }
153     }
154 
155     @Nonnull
156     @Override
157     public ApplicationClassLoader getApplicationClassLoader() {
158         return injector.getInstance(ApplicationClassLoader.class);
159     }
160 
161     @Nonnull
162     @Override
163     public Context getContext() {
164         return injector.getInstance(Context.class, named("applicationContext"));
165     }
166 
167     @Nonnull
168     @Override
169     public Configuration getConfiguration() {
170         return getConfigurationManager().getConfiguration();
171     }
172 
173     @Nonnull
174     @Override
175     public ConfigurationManager getConfigurationManager() {
176         return injector.getInstance(ConfigurationManager.class);
177     }
178 
179     @Nonnull
180     @Override
181     public UIThreadManager getUIThreadManager() {
182         return injector.getInstance(UIThreadManager.class);
183     }
184 
185     @Nonnull
186     @Override
187     public EventRouter getEventRouter() {
188         return injector.getInstance(EventRouter.class, named("applicationEventRouter"));
189     }
190 
191     @Nonnull
192     @Override
193     public ArtifactManager getArtifactManager() {
194         return injector.getInstance(ArtifactManager.class);
195     }
196 
197     @Nonnull
198     @Override
199     public ActionManager getActionManager() {
200         return injector.getInstance(ActionManager.class);
201     }
202 
203     @Nonnull
204     @Override
205     public AddonManager getAddonManager() {
206         return injector.getInstance(AddonManager.class);
207     }
208 
209     @Nonnull
210     @Override
211     public MVCGroupManager getMvcGroupManager() {
212         return injector.getInstance(MVCGroupManager.class);
213     }
214 
215     @Nonnull
216     @Override
217     public MessageSource getMessageSource() {
218         return injector.getInstance(MessageSource.class, named("applicationMessageSource"));
219     }
220 
221     @Nonnull
222     @Override
223     public ResourceResolver getResourceResolver() {
224         return injector.getInstance(ResourceResolver.class, named("applicationResourceResolver"));
225     }
226 
227     @Nonnull
228     @Override
229     public ResourceHandler getResourceHandler() {
230         return injector.getInstance(ResourceHandler.class);
231     }
232 
233     @Nonnull
234     @Override
235     public ResourceInjector getResourceInjector() {
236         return injector.getInstance(ResourceInjector.class, named("applicationResourceInjector"));
237     }
238 
239     @Nonnull
240     @Override
241     public Injector<?> getInjector() {
242         return injector;
243     }
244 
245     public void setInjector(@Nonnull Injector<?> injector) {
246         this.injector = requireNonNull(injector, "Argument 'injector' must not be null");
247         this.injector.injectMembers(this);
248         addShutdownHandler(getWindowManager());
249         MVCGroupExceptionHandler.registerWith(this);
250     }
251 
252     @Nonnull
253     @Override
254     @SuppressWarnings("unchecked")
255     public <W> WindowManager<W> getWindowManager() {
256         return injector.getInstance(WindowManager.class);
257     }
258 
259     protected ApplicationConfigurer getApplicationConfigurer() {
260         return injector.getInstance(ApplicationConfigurer.class);
261     }
262 
263     public void initialize() {
264         if (getPhase() == ApplicationPhase.INITIALIZE) {
265             getApplicationConfigurer().init();
266         }
267     }
268 
269     public void ready() {
270         if (getPhase() != ApplicationPhase.STARTUPreturn;
271 
272         showStartingWindow();
273 
274         setPhase(ApplicationPhase.READY);
275         event(ApplicationEvent.READY_START, asList(this));
276         getApplicationConfigurer().runLifecycleHandler(Lifecycle.READY);
277         event(ApplicationEvent.READY_END, asList(this));
278         setPhase(ApplicationPhase.MAIN);
279     }
280 
281     protected void showStartingWindow() {
282         Object startingWindow = getWindowManager().getStartingWindow();
283         if (startingWindow != null) {
284             getWindowManager().show(startingWindow);
285         }
286     }
287 
288     public boolean canShutdown() {
289         event(ApplicationEvent.SHUTDOWN_REQUESTED, asList(this));
290         synchronized (shutdownLock) {
291             for (ShutdownHandler handler : shutdownHandlers) {
292                 if (!handler.canShutdown(this)) {
293                     event(ApplicationEvent.SHUTDOWN_ABORTED, asList(this));
294                     try {
295                         log.debug("Shutdown aborted by {}", handler);
296                     catch (UnsupportedOperationException uoe) {
297                         log.debug("Shutdown aborted by a handler");
298                     }
299                     return false;
300                 }
301             }
302         }
303         return true;
304     }
305 
306     public boolean shutdown() {
307         // avoids reentrant calls to shutdown()
308         // once permission to quit has been granted
309         if (getPhase() == ApplicationPhase.SHUTDOWNreturn false;
310 
311         if (!canShutdown()) return false;
312         log.info("Shutdown is in process");
313 
314         // signal that shutdown is in process
315         setPhase(ApplicationPhase.SHUTDOWN);
316 
317         // stage 1 - alert all app event handlers
318         // wait for all handlers to complete before proceeding
319         // with stage #2 if and only if the current thread is
320         // the ui thread
321         log.debug("Shutdown stage 1: notify all event listeners");
322         if (getEventRouter().isEventPublishingEnabled()) {
323             final CountDownLatch latch = new CountDownLatch(getUIThreadManager().isUIThread() 0);
324             getEventRouter().addEventListener(ApplicationEvent.SHUTDOWN_START.getName()new RunnableWithArgs() {
325                 @Override
326                 public void run(@Nullable Object... args) {
327                     latch.countDown();
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 CharSequence) {
386             String[] groups = (String.valueOf(startupGroups)).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 }