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