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