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[] args) throws 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.STARTUP) return;
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.SHUTDOWN) return 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() ? 1 : 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.INITIALIZE) return;
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 }
|