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