1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.framework.app;
35
36 import info.magnolia.event.EventBus;
37 import info.magnolia.event.EventBusProtector;
38 import info.magnolia.event.SimpleEventBus;
39 import info.magnolia.i18nsystem.I18nizer;
40 import info.magnolia.i18nsystem.SimpleTranslator;
41 import info.magnolia.module.ModuleRegistry;
42 import info.magnolia.module.model.ModuleDefinition;
43 import info.magnolia.monitoring.SystemMonitor;
44 import info.magnolia.objectfactory.ComponentProvider;
45 import info.magnolia.objectfactory.configuration.ComponentProviderConfiguration;
46 import info.magnolia.objectfactory.configuration.ComponentProviderConfigurationBuilder;
47 import info.magnolia.objectfactory.configuration.InstanceConfiguration;
48 import info.magnolia.objectfactory.guice.AbstractGuiceComponentConfigurer;
49 import info.magnolia.objectfactory.guice.GuiceComponentProvider;
50 import info.magnolia.objectfactory.guice.GuiceComponentProviderBuilder;
51 import info.magnolia.ui.api.app.App;
52 import info.magnolia.ui.api.app.AppContext;
53 import info.magnolia.ui.api.app.AppController;
54 import info.magnolia.ui.api.app.AppDescriptor;
55 import info.magnolia.ui.api.app.AppInstanceController;
56 import info.magnolia.ui.api.app.AppView;
57 import info.magnolia.ui.api.app.SubApp;
58 import info.magnolia.ui.api.app.SubAppContext;
59 import info.magnolia.ui.api.app.SubAppDescriptor;
60 import info.magnolia.ui.api.app.SubAppEventBus;
61 import info.magnolia.ui.api.app.SubAppLifecycleEvent;
62 import info.magnolia.ui.api.app.launcherlayout.AppLauncherGroup;
63 import info.magnolia.ui.api.app.launcherlayout.AppLauncherGroupEntry;
64 import info.magnolia.ui.api.app.launcherlayout.AppLauncherLayoutManager;
65 import info.magnolia.ui.api.context.UiContext;
66 import info.magnolia.ui.api.location.DefaultLocation;
67 import info.magnolia.ui.api.location.Location;
68 import info.magnolia.ui.api.location.LocationController;
69 import info.magnolia.ui.api.message.Message;
70 import info.magnolia.ui.api.overlay.OverlayCloser;
71 import info.magnolia.ui.api.overlay.OverlayLayer;
72 import info.magnolia.ui.api.shell.Shell;
73 import info.magnolia.ui.api.view.View;
74 import info.magnolia.ui.framework.app.stub.FailedAppStub;
75 import info.magnolia.ui.framework.app.stub.FailedSubAppStub;
76 import info.magnolia.ui.framework.context.AbstractUIContext;
77 import info.magnolia.ui.framework.message.MessagesManager;
78 import info.magnolia.ui.framework.overlay.OverlayPresenter;
79 import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
80
81 import java.util.Collection;
82 import java.util.List;
83 import java.util.Map;
84 import java.util.concurrent.ConcurrentHashMap;
85
86 import javax.inject.Inject;
87
88 import org.apache.commons.lang3.StringUtils;
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
91
92 import com.google.inject.name.Names;
93 import com.google.inject.util.Providers;
94
95
96
97
98 public class AppInstanceControllerImpl extends AbstractUIContext implements AppContext, AppInstanceController {
99
100 private static final Logger log = LoggerFactory.getLogger(AppInstanceControllerImpl.class);
101
102
103
104
105 private static final String SUBAPP_PREFIX = "subapp";
106
107 private static class SubAppDetails {
108 private SubAppContext context;
109 private EventBusProtector eventBusProtector;
110 private GuiceComponentProvider componentProvider;
111 private EventBus eventBus;
112 }
113
114 private Map<String, SubAppDetails> subApps = new ConcurrentHashMap<String, SubAppDetails>();
115
116 private ModuleRegistry moduleRegistry;
117
118 private AppController appController;
119
120 private LocationController locationController;
121
122 private Shell shell;
123
124 private MessagesManager messagesManager;
125
126 private ComponentProvider componentProvider;
127
128 private App app;
129
130 private AppDescriptor appDescriptor;
131
132 private SubAppContext currentSubAppContext;
133
134 private AppLauncherLayoutManager appLauncherLayoutManager;
135
136 private final SystemMonitor systemMonitor;
137
138 private final SimpleTranslator i18n;
139
140 @Inject
141 public AppInstanceControllerImpl(ModuleRegistry moduleRegistry, AppController appController, LocationController locationController, Shell shell,
142 MessagesManager messagesManager, AppDescriptor appDescriptor, AppLauncherLayoutManager appLauncherLayoutManager, SystemMonitor systemMonitor, I18nizer i18nizer, SimpleTranslator i18n) {
143 this.moduleRegistry = moduleRegistry;
144 this.appController = appController;
145 this.locationController = locationController;
146 this.shell = shell;
147 this.messagesManager = messagesManager;
148 this.appDescriptor = i18nizer.decorate(appDescriptor);
149 this.appLauncherLayoutManager = appLauncherLayoutManager;
150 this.systemMonitor = systemMonitor;
151 this.i18n = i18n;
152 }
153
154 @Override
155 protected OverlayPresenter initializeOverlayPresenter() {
156 return new OverlayPresenter() {
157 @Override
158 public OverlayCloser openOverlay(View view, ModalityLevel modalityLevel) {
159 View overlayParent = getView();
160 return AppInstanceControllerImpl.this.shell.openOverlayOnView(view, overlayParent, OverlayLayer.ModalityDomain.APP, modalityLevel);
161 }
162 };
163 }
164
165 @Override
166 public void setAppComponentProvider(ComponentProvider componentProvider) {
167 this.componentProvider = componentProvider;
168 }
169
170 @Override
171 public void setApp(App app) {
172 this.app = app;
173 }
174
175 @Override
176 public App getApp() {
177 return app;
178 }
179
180 @Override
181 public String getName() {
182 return appDescriptor.getName();
183 }
184
185 @Override
186 public String getLabel() {
187 return appDescriptor.getLabel();
188 }
189
190 @Override
191 public AppDescriptor getAppDescriptor() {
192 return appDescriptor;
193 }
194
195 @Override
196 public SubAppDescriptor getDefaultSubAppDescriptor() {
197 Collection<SubAppDescriptor> subAppDescriptors = getAppDescriptor().getSubApps().values();
198 return subAppDescriptors.isEmpty() ? null : subAppDescriptors.iterator().next();
199 }
200
201 private SubAppDescriptor getSubAppDescriptorById(String subAppId) {
202 Map<String, SubAppDescriptor> subAppDescriptors = getAppDescriptor().getSubApps();
203 return subAppDescriptors.get(subAppId);
204 }
205
206 @Override
207 public AppView getView() {
208 return app.getView();
209 }
210
211
212
213
214
215 @Override
216 public void start(Location location) {
217 if (systemMonitor.isMemoryLimitReached()) {
218 String memoryMessageCloseApps = i18n.translate("ui-framework.appInstanceController.memoryLimitWarningMessage.closeApps");
219 shell.openNotification(MessageStyleTypeEnum.WARNING, false, i18n.translate("ui-framework.memoryLimitWarningMessage.template",memoryMessageCloseApps));
220 }
221
222 try {
223 app = componentProvider.newInstance(appDescriptor.getAppClass());
224 app.start(location);
225
226 if (StringUtils.isNotBlank(appDescriptor.getTheme())) {
227 app.getView().setTheme(appDescriptor.getTheme());
228 }
229 } catch (final Exception e) {
230 log.error("App {} failed to start: {}", appDescriptor.getName(), e.getMessage(), e);
231 app = componentProvider.newInstance(FailedAppStub.class, this, e);
232 app.start(location);
233 }
234
235
236 if (StringUtils.isNotBlank(appDescriptor.getIcon())) {
237 for (AppLauncherGroup group : appLauncherLayoutManager.getLayoutForCurrentUser().getGroups()) {
238 for (AppLauncherGroupEntry entry : group.getApps()) {
239 if (entry.getName().equals(this.getAppDescriptor().getName())) {
240 app.getView().setAppLogo(getAppDescriptor().getIcon(), group.getColor());
241 }
242 }
243 }
244 }
245 }
246
247
248
249
250 @Override
251 public void onLocationUpdate(Location location) {
252 app.locationChanged(location);
253 }
254
255 @Override
256 public void onFocus(String instanceId) {
257 if (subApps.containsKey(instanceId)) {
258
259
260
261 if (currentSubAppContext != null) {
262 sendSubAppLifecycleEvent(currentSubAppContext, SubAppLifecycleEvent.Type.BLURRED);
263 }
264 SubAppContext subAppContext = subApps.get(instanceId).context;
265 locationController.goTo(subAppContext.getLocation());
266 sendSubAppLifecycleEvent(subAppContext, SubAppLifecycleEvent.Type.FOCUSED);
267 }
268 }
269
270 private void sendSubAppLifecycleEvent(SubAppContext ctx, SubAppLifecycleEvent.Type type) {
271 getSubAppEventBus(ctx).fireEvent(new SubAppLifecycleEvent(ctx.getSubAppDescriptor(), type));
272 }
273
274 private EventBus getSubAppEventBus(SubAppContext subAppContext) {
275 return subApps.get(subAppContext.getInstanceId()).eventBus;
276 }
277
278 @Override
279 public void onClose(String instanceId) {
280 stopSubAppInstance(instanceId);
281 onFocus(app.getView().getActiveSubAppView());
282 }
283
284 @Override
285 public String mayStop() {
286 return null;
287 }
288
289 @Override
290 public void stop() {
291 for (String instanceId : subApps.keySet()) {
292 stopSubAppInstance(instanceId);
293 }
294 currentSubAppContext = null;
295 if (app != null) {
296 app.stop();
297 } else {
298 log.warn("Cannot call stop on app that's already set to null. Name: {}, Thread: {}", getName(), Thread.currentThread().getName());
299 }
300 }
301
302 private void stopSubAppInstance(String instanceId) {
303 SubAppDetails subAppDetails = subApps.get(instanceId);
304
305
306
307
308 sendSubAppLifecycleEvent(subAppDetails.context, SubAppLifecycleEvent.Type.STOPPED);
309
310 subAppDetails.context.getSubApp().stop();
311 subAppDetails.componentProvider.destroy();
312 subAppDetails.eventBusProtector.resetEventBuses();
313 subApps.remove(instanceId);
314 }
315
316 @Override
317 public Location getCurrentLocation() {
318 SubAppContext subAppContext = getActiveSubAppContext();
319 if (subAppContext != null) {
320 return subAppContext.getLocation();
321 }
322 return new DefaultLocation(Location.LOCATION_TYPE_APP, appDescriptor.getName());
323 }
324
325 @Override
326 public Location getDefaultLocation() {
327 SubAppDescriptor subAppDescriptor = getDefaultSubAppDescriptor();
328 if (subAppDescriptor != null) {
329 return new DefaultLocation(Location.LOCATION_TYPE_APP, appDescriptor.getName(), subAppDescriptor.getName());
330 } else {
331 return null;
332 }
333 }
334
335 @Override
336 public void openSubApp(Location location) {
337
338 final Location defaultLocation = getDefaultLocation();
339 boolean isDefaultSubApp = false;
340
341 if (defaultLocation != null) {
342 isDefaultSubApp = defaultLocation.getSubAppId().equals(location.getSubAppId());
343 if (!isDefaultSubApp) {
344 SubAppContext subAppContext = getSupportingSubAppContext(defaultLocation);
345 if (subAppContext == null) {
346 startSubApp(defaultLocation, false);
347 }
348 }
349 }
350
351
352
353 SubAppContext subAppContext = getSupportingSubAppContext(location);
354 if (subAppContext != null) {
355 subAppContext.getSubApp().locationChanged(location);
356 subAppContext.setLocation(location);
357
358 getView().updateCaption(subAppContext.getInstanceId(), subAppContext.getSubApp().getCaption());
359
360 if (!subAppContext.getInstanceId().equals(app.getView().getActiveSubAppView())) {
361 app.getView().setActiveSubAppView(subAppContext.getInstanceId());
362 }
363 } else {
364 subAppContext = startSubApp(location, !isDefaultSubApp);
365 }
366 currentSubAppContext = subAppContext;
367
368 }
369
370
371
372
373
374 @Override
375 public void closeSubApp(String instanceId) {
376 getView().closeSubAppView(instanceId);
377 }
378
379 private SubAppContext startSubApp(Location location, boolean allowClose) {
380
381 SubAppDescriptor subAppDescriptor = getSubAppDescriptorById(location.getSubAppId());
382
383 if (subAppDescriptor == null) {
384 subAppDescriptor = getDefaultSubAppDescriptor();
385 if (subAppDescriptor == null) {
386 log.warn("No subapp could be found for the '{}' app, please check configuration.", appDescriptor.getName());
387 return null;
388 }
389 }
390 SubAppContext subAppContext = new SubAppContextImpl(subAppDescriptor, shell);
391
392 subAppContext.setAppContext(this);
393 subAppContext.setLocation(location);
394
395 SubAppDetails subAppDetails = createSubAppComponentProvider(appDescriptor.getName(), subAppContext.getSubAppId(), subAppContext, componentProvider);
396 subAppDetails.context = subAppContext;
397
398 Class<? extends SubApp> subAppClass = subAppDescriptor.getSubAppClass();
399 if (subAppClass == null) {
400 log.warn("Sub App {} doesn't define its sub app class or class doesn't exist or can't be instantiated.", subAppDescriptor.getName());
401 } else {
402 SubApp subApp;
403 View subAppView;
404 boolean closable = true;
405 try {
406 subApp = subAppDetails.componentProvider.newInstance(subAppClass);
407 subAppContext.setSubApp(subApp);
408 subAppView = subApp.start(location);
409 closable = allowClose && subApp.isCloseable();
410 } catch (Exception e) {
411 log.error("Sub-app {} failed to start: {}", subAppDescriptor.getName(), e.getMessage(), e);
412 closeSubApp(subAppDescriptor.getName());
413 subAppDetails.eventBusProtector.resetEventBuses();
414
415 subApp = subAppDetails.componentProvider.newInstance(FailedSubAppStub.class, e);
416 subAppView = subApp.start(location);
417 subAppContext.setSubApp(subApp);
418 }
419
420 String instanceId = app.getView().addSubAppView(subAppView, subApp.getCaption(), closable);
421
422 subAppContext.setInstanceId(instanceId);
423
424 subApps.put(instanceId, subAppDetails);
425
426 sendSubAppLifecycleEvent(subAppContext, SubAppLifecycleEvent.Type.STARTED);
427 }
428 return subAppContext;
429 }
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447 @Override
448 public void updateSubAppLocation(SubAppContext subAppContext, Location location) {
449 subAppContext.setLocation(location);
450
451
452 if (subAppContext.getInstanceId() != null) {
453 getView().updateCaption(subAppContext.getInstanceId(), subAppContext.getSubApp().getCaption());
454 }
455
456 if (appController.getCurrentApp() == getApp() && getActiveSubAppContext() == subAppContext) {
457 shell.setFragment(location.toString());
458 }
459 }
460
461 @Override
462 public void sendUserMessage(final String user, final Message message) {
463 messagesManager.sendMessage(user, message);
464 }
465
466 @Override
467 public void sendGroupMessage(final String group, final Message message) {
468 messagesManager.sendGroupMessage(group, message);
469 }
470
471 @Override
472 public void sendLocalMessage(Message message) {
473 messagesManager.sendLocalMessage(message);
474 }
475
476 @Override
477 public void broadcastMessage(Message message) {
478 messagesManager.broadcastMessage(message);
479 }
480
481 @Override
482 public void showConfirmationMessage(String message) {
483 log.info("If confirmation message was already implemented you'd get a {} now...", message);
484 }
485
486 @Override
487 public SubAppContext getActiveSubAppContext() {
488 return currentSubAppContext;
489 }
490
491
492
493
494
495 private SubAppContext getSupportingSubAppContext(Location location) {
496 SubAppContext supportingContext = null;
497
498
499 if (getDefaultSubAppDescriptor() != null) {
500 String subAppId = (location.getSubAppId().isEmpty()) ? getDefaultSubAppDescriptor().getName() : location.getSubAppId();
501
502 for (SubAppDetails subAppDetails : subApps.values()) {
503 SubAppContext context = subAppDetails.context;
504 if (!subAppId.equals(context.getSubAppId())) {
505 continue;
506 }
507 if (context.getSubApp().supportsLocation(location)) {
508 supportingContext = context;
509 break;
510 }
511 }
512 }
513
514 return supportingContext;
515 }
516
517 private SubAppDetails createSubAppComponentProvider(String appName, String subAppName, SubAppContext subAppContext, ComponentProvider parent) {
518
519 SubAppDetails subAppDetails = new SubAppDetails();
520
521 ComponentProviderConfigurationBuilder configurationBuilder = new ComponentProviderConfigurationBuilder();
522 List<ModuleDefinition> moduleDefinitions = moduleRegistry.getModuleDefinitions();
523
524
525 ComponentProviderConfiguration configuration = configurationBuilder.getComponentsFromModules(SUBAPP_PREFIX, moduleDefinitions);
526
527
528 String componentsId = "app-" + appName + "-" + subAppName;
529 log.debug("Reading component configurations from module descriptors for " + componentsId);
530 ComponentProviderConfiguration subAppComponents = configurationBuilder.getComponentsFromModules(componentsId, moduleDefinitions);
531
532 configuration.combine(subAppComponents);
533
534
535 configuration.addComponent(InstanceConfiguration.valueOf(SubAppContext.class, subAppContext));
536 configuration.addComponent(InstanceConfiguration.valueOf(UiContext.class, subAppContext));
537 final EventBus eventBus = new SimpleEventBus();
538 configuration.addConfigurer(new AbstractGuiceComponentConfigurer() {
539
540 @Override
541 protected void configure() {
542 bind(EventBus.class).annotatedWith(Names.named(SubAppEventBus.NAME)).toProvider(Providers.of(eventBus));
543 }
544 });
545
546 EventBusProtector eventBusProtector = new EventBusProtector();
547 configuration.addConfigurer(eventBusProtector);
548 subAppDetails.eventBusProtector = eventBusProtector;
549 subAppDetails.eventBus = eventBus;
550
551 log.debug("Creating component provider for sub app " + subAppName);
552 GuiceComponentProviderBuilder builder = new GuiceComponentProviderBuilder();
553 builder.withConfiguration(configuration);
554 builder.withParent((GuiceComponentProvider) parent);
555
556 subAppDetails.componentProvider = builder.build();
557
558 return subAppDetails;
559 }
560 }