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