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.lang.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 app.stop();
279 }
280
281 private void stopSubAppInstance(String instanceId) {
282 SubAppDetails subAppDetails = subApps.get(instanceId);
283 if (subAppDetails == null) {
284 return;
285 }
286 subAppDetails.context.getSubApp().stop();
287 subAppDetails.componentProvider.destroy();
288 subAppDetails.eventBusProtector.resetEventBuses();
289 subApps.remove(instanceId);
290 }
291
292 @Override
293 public Location getCurrentLocation() {
294 SubAppContext subAppContext = getActiveSubAppContext();
295 if (subAppContext != null) {
296 return subAppContext.getLocation();
297 }
298 return new DefaultLocation(Location.LOCATION_TYPE_APP, appDescriptor.getName());
299 }
300
301 @Override
302 public Location getDefaultLocation() {
303 SubAppDescriptor subAppDescriptor = getDefaultSubAppDescriptor();
304 if (subAppDescriptor != null) {
305 return new DefaultLocation(Location.LOCATION_TYPE_APP, appDescriptor.getName(), subAppDescriptor.getName());
306 } else {
307 return null;
308 }
309 }
310
311 @Override
312 public void openSubApp(Location location) {
313
314 final Location defaultLocation = getDefaultLocation();
315 boolean isDefaultSubApp = false;
316
317 if (defaultLocation != null) {
318 isDefaultSubApp = defaultLocation.getSubAppId().equals(location.getSubAppId());
319 if (!isDefaultSubApp) {
320 SubAppContext subAppContext = getSupportingSubAppContext(defaultLocation);
321 if (subAppContext == null) {
322 startSubApp(defaultLocation, false);
323 }
324 }
325 }
326
327
328
329 SubAppContext subAppContext = getSupportingSubAppContext(location);
330 if (subAppContext != null) {
331 subAppContext.getSubApp().locationChanged(location);
332 subAppContext.setLocation(location);
333
334 getView().updateCaption(subAppContext.getInstanceId(), subAppContext.getSubApp().getCaption());
335
336
337 if (!subAppContext.getInstanceId().equals(app.getView().getActiveSubAppView())) {
338 app.getView().setActiveSubAppView(subAppContext.getInstanceId());
339 }
340 } else {
341 subAppContext = startSubApp(location, !isDefaultSubApp);
342 }
343 currentSubAppContext = subAppContext;
344
345 }
346
347
348
349
350
351 @Override
352 public void closeSubApp(String instanceId) {
353 getView().closeSubAppView(instanceId);
354 }
355
356 private SubAppContext startSubApp(Location location, boolean allowClose) {
357
358 SubAppDescriptor subAppDescriptor = getSubAppDescriptorById(location.getSubAppId());
359
360 if (subAppDescriptor == null) {
361 subAppDescriptor = getDefaultSubAppDescriptor();
362 if (subAppDescriptor == null) {
363 log.warn("No subapp could be found for the '{}' app, please check configuration.", appDescriptor.getName());
364 return null;
365 }
366 }
367 SubAppContext subAppContext = new SubAppContextImpl(subAppDescriptor, shell);
368
369 subAppContext.setAppContext(this);
370 subAppContext.setLocation(location);
371
372 SubAppDetails subAppDetails = createSubAppComponentProvider(appDescriptor.getName(), subAppContext.getSubAppId(), subAppContext, componentProvider);
373 subAppDetails.context = subAppContext;
374
375 Class<? extends SubApp> subAppClass = subAppDescriptor.getSubAppClass();
376 if (subAppClass == null) {
377 log.warn("Sub App {} doesn't define its sub app class or class doesn't exist or can't be instantiated.", subAppDescriptor.getName());
378 } else {
379 SubApp subApp;
380 View subAppView;
381 boolean closable = true;
382 try {
383 subApp = subAppDetails.componentProvider.newInstance(subAppClass);
384 subAppContext.setSubApp(subApp);
385 subAppView = subApp.start(location);
386 closable = allowClose && subApp.isCloseable();
387 } catch (Exception e) {
388 log.error("Sub-app {} failed to start: {}", subAppDescriptor.getName(), e.getMessage(), e);
389 closeSubApp(subAppDescriptor.getName());
390 subAppDetails.eventBusProtector.resetEventBuses();
391
392 subApp = subAppDetails.componentProvider.newInstance(FailedSubAppStub.class, e);
393 subAppView = subApp.start(location);
394 subAppContext.setSubApp(subApp);
395 }
396
397 String instanceId = app.getView().addSubAppView(subAppView, subApp.getCaption(), closable);
398
399 subAppContext.setInstanceId(instanceId);
400
401 subApps.put(instanceId, subAppDetails);
402 }
403 return subAppContext;
404 }
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422 @Override
423 public void updateSubAppLocation(SubAppContext subAppContext, Location location) {
424 subAppContext.setLocation(location);
425
426
427 if (subAppContext.getInstanceId() != null) {
428 getView().updateCaption(subAppContext.getInstanceId(), subAppContext.getSubApp().getCaption());
429 }
430
431 if (appController.getCurrentApp() == getApp() && getActiveSubAppContext() == subAppContext) {
432 shell.setFragment(location.toString());
433 }
434 }
435
436 @Override
437 public void sendUserMessage(final String user, final Message message) {
438 messagesManager.sendMessage(user, message);
439 }
440
441 @Override
442 public void sendGroupMessage(final String group, final Message message) {
443 messagesManager.sendGroupMessage(group, message);
444 }
445
446 @Override
447 public void sendLocalMessage(Message message) {
448 messagesManager.sendLocalMessage(message);
449 }
450
451 @Override
452 public void broadcastMessage(Message message) {
453 messagesManager.broadcastMessage(message);
454 }
455
456 @Override
457 public void showConfirmationMessage(String message) {
458 log.info("If confirmation message was already implemented you'd get a {} now...", message);
459 }
460
461 @Override
462 public SubAppContext getActiveSubAppContext() {
463 return currentSubAppContext;
464 }
465
466
467
468
469
470 private SubAppContext getSupportingSubAppContext(Location location) {
471 SubAppContext supportingContext = null;
472
473
474 if (getDefaultSubAppDescriptor() != null) {
475 String subAppId = (location.getSubAppId().isEmpty()) ? getDefaultSubAppDescriptor().getName() : location.getSubAppId();
476
477 for (SubAppDetails subAppDetails : subApps.values()) {
478 SubAppContext context = subAppDetails.context;
479 if (!subAppId.equals(context.getSubAppId())) {
480 continue;
481 }
482 if (context.getSubApp().supportsLocation(location)) {
483 supportingContext = context;
484 break;
485 }
486 }
487 }
488
489 return supportingContext;
490 }
491
492 private SubAppDetails createSubAppComponentProvider(String appName, String subAppName, SubAppContext subAppContext, ComponentProvider parent) {
493
494 SubAppDetails subAppDetails = new SubAppDetails();
495
496 ComponentProviderConfigurationBuilder configurationBuilder = new ComponentProviderConfigurationBuilder();
497 List<ModuleDefinition> moduleDefinitions = moduleRegistry.getModuleDefinitions();
498
499
500 ComponentProviderConfiguration configuration = configurationBuilder.getComponentsFromModules(SUBAPP_PREFIX, moduleDefinitions);
501
502
503 String componentsId = "app-" + appName + "-" + subAppName;
504 log.debug("Reading component configurations from module descriptors for " + componentsId);
505 ComponentProviderConfiguration subAppComponents = configurationBuilder.getComponentsFromModules(componentsId, moduleDefinitions);
506
507 configuration.combine(subAppComponents);
508
509
510 configuration.addComponent(InstanceConfiguration.valueOf(SubAppContext.class, subAppContext));
511 configuration.addComponent(InstanceConfiguration.valueOf(UiContext.class, subAppContext));
512
513 configuration.addConfigurer(new AbstractGuiceComponentConfigurer() {
514
515 @Override
516 protected void configure() {
517 bind(EventBus.class).annotatedWith(Names.named(SubAppEventBus.NAME)).toProvider(Providers.of(new SimpleEventBus()));
518 }
519 });
520
521 EventBusProtector eventBusProtector = new EventBusProtector();
522 configuration.addConfigurer(eventBusProtector);
523 subAppDetails.eventBusProtector = eventBusProtector;
524
525 log.debug("Creating component provider for sub app " + subAppName);
526 GuiceComponentProviderBuilder builder = new GuiceComponentProviderBuilder();
527 builder.withConfiguration(configuration);
528 builder.withParent((GuiceComponentProvider) parent);
529
530 subAppDetails.componentProvider = builder.build();
531
532 return subAppDetails;
533 }
534 }