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