001 /*
002 * Copyright 2008-2015 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[] args) throws 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.STARTUP) return;
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.SHUTDOWN) return 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() ? 1 : 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.INITIALIZE) return;
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 = ((String) startupGroups).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 }
|