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