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