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