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