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.cms.security.operations.AccessDefinition;
38 import info.magnolia.config.registry.DefinitionProvider;
39 import info.magnolia.config.registry.Registry;
40 import info.magnolia.context.Context;
41 import info.magnolia.event.EventBus;
42 import info.magnolia.i18nsystem.SimpleTranslator;
43 import info.magnolia.objectfactory.ComponentProvider;
44 import info.magnolia.objectfactory.Components;
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.AppLifecycleEvent;
51 import info.magnolia.ui.api.app.AppLifecycleEventType;
52 import info.magnolia.ui.api.app.ChooseDialogCallback;
53 import info.magnolia.ui.api.app.registry.AppDescriptorRegistry;
54 import info.magnolia.ui.api.context.UiContext;
55 import info.magnolia.ui.api.event.AdmincentralEventBus;
56 import info.magnolia.ui.api.ioc.AdmincentralScoped;
57 import info.magnolia.ui.api.location.DefaultLocation;
58 import info.magnolia.ui.api.location.Location;
59 import info.magnolia.ui.api.location.LocationChangeRequestedEvent;
60 import info.magnolia.ui.api.location.LocationChangedEvent;
61 import info.magnolia.ui.api.location.LocationController;
62 import info.magnolia.ui.api.message.Message;
63 import info.magnolia.ui.api.message.MessageType;
64 import info.magnolia.ui.api.shell.CloseAppEvent;
65 import info.magnolia.ui.api.view.Viewport;
66 import info.magnolia.ui.framework.app.stub.FailedAppStub;
67 import info.magnolia.ui.framework.ioc.AdmincentralFlavour;
68 import info.magnolia.ui.framework.ioc.SessionStore;
69 import info.magnolia.ui.framework.ioc.UiContextBoundComponentProvider;
70 import info.magnolia.ui.framework.ioc.UiContextReference;
71 import info.magnolia.ui.framework.message.MessagesManager;
72
73 import java.util.HashMap;
74 import java.util.LinkedList;
75 import java.util.Map;
76 import java.util.Objects;
77 import java.util.Optional;
78
79 import javax.inject.Inject;
80 import javax.inject.Named;
81
82 import org.apache.commons.lang3.StringUtils;
83 import org.slf4j.Logger;
84 import org.slf4j.LoggerFactory;
85
86 import com.google.inject.Key;
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 @AdmincentralScoped
104 public class AppControllerImpl implements AppController, LocationChangedEvent.Handler, LocationChangeRequestedEvent.Handler, CloseAppEvent.Handler {
105
106 private static final Logger log = LoggerFactory.getLogger(AppControllerImpl.class);
107
108 private final ComponentProvider componentProvider;
109 private final AppDescriptorRegistry appDescriptorRegistry;
110 private final LocationController locationController;
111 private final EventBus eventBus;
112 private final Map<String, AppInstanceController> runningApps = new HashMap<>();
113 private final LinkedList<AppInstanceController> appHistory = new LinkedList<>();
114 private final MessagesManager messagesManager;
115 private final SimpleTranslator i18n;
116 private final User user;
117
118 private Map<String, AppInstanceController> appInstances = new HashMap<>();
119 private Viewport viewport;
120 private AppInstanceController currentAppInstanceController;
121
122 @Inject
123 public AppControllerImpl(ComponentProvider componentProvider, AppDescriptorRegistry appDescriptorRegistry, LocationController locationController, @Named(AdmincentralEventBus.NAME) EventBus admincentralEventBus, MessagesManager messagesManager, SimpleTranslator i18n, Context context) {
124 this.componentProvider = componentProvider;
125 this.appDescriptorRegistry = appDescriptorRegistry;
126 this.locationController = locationController;
127 this.eventBus = admincentralEventBus;
128 this.messagesManager = messagesManager;
129 this.i18n = i18n;
130 this.user = context.getUser();
131
132 admincentralEventBus.addHandler(LocationChangedEvent.class, this);
133 admincentralEventBus.addHandler(LocationChangeRequestedEvent.class, this);
134 admincentralEventBus.addHandler(CloseAppEvent.class, this);
135 }
136
137
138
139
140 @Deprecated
141 public AppControllerImpl(ComponentProvider componentProvider, AppDescriptorRegistry appDescriptorRegistry, LocationController locationController, @Named(AdmincentralEventBus.NAME) EventBus admincentralEventBus, MessagesManager messagesManager, SimpleTranslator i18n) {
142 this(componentProvider, appDescriptorRegistry, locationController, admincentralEventBus, messagesManager, i18n, Components.getComponent(Context.class));
143 }
144
145 @Override
146 public void setViewport(Viewport viewport) {
147 this.viewport = viewport;
148 }
149
150 @Override
151 public void onCloseApp(CloseAppEvent event) {
152 stopCurrentApp();
153 }
154
155
156
157
158
159
160
161
162 private App getAppWithoutStarting(String appName) {
163 AppInstanceController appInstanceController = createNewAppInstance(appName);
164 ComponentProvider appComponentProvider = createAppComponentProvider(appInstanceController);
165 App app = appComponentProvider.newInstance(appInstanceController.getAppDescriptor().getAppClass());
166
167 appInstanceController.setApp(app);
168 return app;
169 }
170
171
172
173
174
175
176
177
178 public App startIfNotAlreadyRunningThenFocus(String appName, Location location) {
179 AppInstanceController appInstanceController = getAppInstance(appName);
180 appInstanceController = doStartIfNotAlreadyRunning(appInstanceController, location);
181 doFocus(appInstanceController);
182 return appInstanceController.getApp();
183 }
184
185 @Override
186 public void stopApp(String appName) {
187 final AppInstanceController appInstanceController = runningApps.get(appName);
188 if (appInstanceController != null) {
189 doStop(appInstanceController);
190 }
191 }
192
193 @Override
194 public void stopCurrentApp() {
195 if (currentAppInstanceController.isHeaderApp()) {
196 stopApp(currentAppInstanceController.getName());
197 } else {
198 final AppInstanceController appInstanceController = appHistory.peekFirst();
199 if (appInstanceController != null) {
200 doStop(appInstanceController);
201 }
202 }
203 }
204
205 @Override
206 public boolean isAppStarted(String appName) {
207 return runningApps.containsKey(appName);
208 }
209
210 @Override
211 public void focusCurrentApp() {
212 if (currentAppInstanceController != null) {
213 doFocus(currentAppInstanceController);
214 }
215 }
216
217 @Override
218 public App getCurrentApp() {
219 return currentAppInstanceController == null ? null : currentAppInstanceController.getApp();
220 }
221
222
223
224
225
226
227 @Override
228 public Location getCurrentAppLocation() {
229 return currentAppInstanceController == null ? null : currentAppInstanceController.getCurrentLocation();
230 }
231
232
233
234
235 @Override
236 public Location getAppLocation(String appName) {
237 AppInstanceController appInstanceController = runningApps.get(appName);
238 return appInstanceController == null ? null : appInstanceController.getCurrentLocation();
239 }
240
241
242
243
244
245 private AppInstanceController doStartIfNotAlreadyRunning(AppInstanceController appInstanceController, Location location) {
246 if (isAppStarted(appInstanceController.getAppDescriptor().getName())) {
247 appInstanceController.onLocationUpdate(location);
248 sendEvent(AppLifecycleEventType.FOCUSED, appInstanceController.getAppDescriptor());
249 return appInstanceController;
250 }
251
252 runningApps.put(appInstanceController.getAppDescriptor().getName(), appInstanceController);
253 appInstanceController.start(location);
254 sendEvent(AppLifecycleEventType.STARTED, appInstanceController.getAppDescriptor());
255 return appInstanceController;
256 }
257
258
259
260
261
262 private void doFocus(AppInstanceController appInstanceController) {
263 locationController.goTo(appInstanceController.getCurrentLocation());
264
265
266 }
267
268 private void doStop(AppInstanceController appInstanceController) {
269 final Location currentLocation = locationController.getWhere();
270
271 sendEvent(AppLifecycleEventType.STOPPED, appInstanceController.getAppDescriptor());
272 appInstanceController.stop();
273 while (appHistory.remove(appInstanceController)) {
274 ;
275 }
276
277 final String appName = appInstanceController.getAppDescriptor().getName();
278 runningApps.remove(appName);
279 appInstances.remove(appName);
280
281 if (currentAppInstanceController == appInstanceController) {
282 currentAppInstanceController = null;
283 viewport.setView(null);
284 }
285
286 if (!appHistory.isEmpty()) {
287 doFocus(appHistory.peekFirst());
288 } else if (!AdmincentralFlavour.get().isM5()) {
289
290 locationController.goTo(new DefaultLocation("home"));
291 }
292
293 if (AdmincentralFlavour.get().isM5() &&
294 StringUtils.equalsIgnoreCase(appName, currentLocation.getAppName())) {
295
296 locationController.goTo(new DefaultLocation(Location.LOCATION_TYPE_SHELL_APP, "applauncher"));
297 }
298 }
299
300 private void sendEvent(AppLifecycleEventType appEventType, AppDescriptor appDescriptor) {
301 eventBus.fireEvent(new AppLifecycleEvent(appDescriptor, appEventType));
302 }
303
304
305
306
307
308
309
310
311
312
313
314
315 @Override
316 public void onLocationChanged(LocationChangedEvent event) {
317 Location newLocation = event.getNewLocation();
318
319
320
321 String actualParam = newLocation.getParameter();
322 if (StringUtils.isEmpty(actualParam) && newLocation instanceof DefaultLocation) {
323 Location lastAppLocation = getAppLocation(newLocation.getAppName());
324 if (lastAppLocation != null) {
325 ((DefaultLocation) newLocation).setParameter(lastAppLocation.getParameter());
326 }
327 }
328
329 if (locationIsEmpty(newLocation)) {
330 return;
331 }
332
333 if (!newLocation.getAppType().equals(Location.LOCATION_TYPE_APP)) {
334 return;
335 }
336
337 if (newLocation.equals(getCurrentAppLocation())) {
338 return;
339 }
340
341 if (!isAllowedToUser(newLocation)) {
342 return;
343 }
344
345 AppDescriptor nextApp = getAppForLocation(newLocation);
346 if (nextApp == null) {
347 return;
348 }
349
350 AppInstanceController nextAppContext = getAppInstance(nextApp.getName());
351
352
353 Location updateLocation = updateLocation(nextAppContext, newLocation);
354 if (!updateLocation.equals(newLocation)) {
355 locationController.goTo(updateLocation);
356 return;
357 }
358
359 if (currentAppInstanceController != nextAppContext) {
360 if (!nextAppContext.isHeaderApp()) {
361 appHistory.addFirst(nextAppContext);
362 }
363 currentAppInstanceController = nextAppContext;
364 }
365
366 nextAppContext = doStartIfNotAlreadyRunning(nextAppContext, newLocation);
367
368 try {
369 viewport.setView(nextAppContext.getApp().getView());
370 } catch (Exception e) {
371 log.error("App {} failed to start: {}", nextApp.getName(), e.getMessage(), e);
372 App failedApp = componentProvider.newInstance(FailedAppStub.class, nextAppContext, e);
373 failedApp.start(newLocation);
374 viewport.setView(failedApp.getView());
375 }
376 }
377
378
379
380
381
382
383
384
385 private Location updateLocation(AppInstanceController appInstanceController, Location location) {
386 String appType = location.getAppType();
387 String appName = location.getAppName();
388 String subAppId = location.getSubAppId();
389 String params = location.getParameter();
390
391 if (StringUtils.isBlank(subAppId)) {
392
393 if (isAppStarted(appName)) {
394 AppInstanceController runningAppContext = runningApps.get(appName);
395 subAppId = runningAppContext.getCurrentLocation().getSubAppId();
396 } else if (StringUtils.isBlank(subAppId)) {
397 Location defaultLocation = appInstanceController.getDefaultLocation();
398 if (defaultLocation != null) {
399 subAppId = defaultLocation.getSubAppId();
400 } else {
401 log.warn("No default location could be found for the '{}' app, please check subapp configuration.", appName);
402 }
403
404 }
405 }
406
407 return new DefaultLocation(appType, appName, subAppId, params);
408 }
409
410 private AppInstanceController getAppInstance(String appName) {
411 return appInstances.computeIfAbsent(appName, this::createNewAppInstance);
412 }
413
414 private AppInstanceController createNewAppInstance(String appName) {
415 AppDescriptor descriptor = getAppDescriptor(appName);
416 if (descriptor == null) {
417 sendAppDescriptorReadErrorNotification(appName);
418 return null;
419 }
420
421 AppInstanceController appInstanceController = componentProvider.newInstance(AppInstanceController.class, descriptor);
422 createAppComponentProvider(appInstanceController);
423
424 return appInstanceController;
425 }
426
427 @Override
428 public void onLocationChangeRequested(LocationChangeRequestedEvent event) {
429 if (currentAppInstanceController != null) {
430 final String message = currentAppInstanceController.mayStop();
431 if (message != null) {
432 event.setWarning(message);
433 }
434 }
435 }
436
437 @Override
438 public void openChooseDialog(String appName, UiContext uiContext, String selectedId, ChooseDialogCallback callback) {
439 openChooseDialog(appName, uiContext, null, selectedId, callback);
440 }
441
442 @Override
443 public void openChooseDialog(String appName, UiContext uiContext, String targetTreeRootPath, String selectedId, ChooseDialogCallback callback) {
444 App targetApp = getAppWithoutStarting(appName);
445 if (targetApp != null) {
446 final ChooseDialogCallback composedCallback = ChooseDialogCallback.composeWith(targetApp::stop, callback);
447 if (StringUtils.isNotBlank(targetTreeRootPath)) {
448 targetApp.openChooseDialog(uiContext, targetTreeRootPath, selectedId, composedCallback);
449 } else {
450 targetApp.openChooseDialog(uiContext, selectedId, callback);
451 }
452 }
453 }
454
455 @Override
456 public Optional<AppContext> getCurrentAppContext() {
457 return Optional.ofNullable(this.currentAppInstanceController);
458 }
459
460 @Override
461 public Map<String, AppContext> getRunningApps() {
462 return (Map) this.appInstances;
463 }
464
465 private AppDescriptor getAppForLocation(Location location) {
466 return getAppDescriptor(location.getAppName());
467 }
468
469 private AppDescriptor getAppDescriptor(String name) throws RuntimeException {
470 final DefinitionProvider<AppDescriptor> definitionProvider;
471 try {
472 definitionProvider = appDescriptorRegistry.getProvider(name);
473 } catch (Registry.NoSuchDefinitionException | IllegalStateException e) {
474 sendAppDescriptorReadErrorNotification(name);
475 throw new RuntimeException(e);
476 }
477 return definitionProvider.get();
478 }
479
480 private void sendAppDescriptorReadErrorNotification(String name) {
481 Message errorMessage = new Message();
482 errorMessage.setType(MessageType.ERROR);
483 errorMessage.setSubject(i18n.translate("ui-framework.app.appdescriptorReadError.subject"));
484 errorMessage.setMessage(String.format(i18n.translate("ui-framework.app.appdescriptorReadError.message"), name));
485 messagesManager.sendLocalMessage(errorMessage);
486 }
487
488 private ComponentProvider createAppComponentProvider(AppInstanceController appInstanceController) {
489 final UiContextReference uiContextReference = UiContextReference.ofApp(appInstanceController);
490 final UiContextBoundComponentProvidertml#UiContextBoundComponentProvider">UiContextBoundComponentProvider appComponentProvider = new UiContextBoundComponentProvider(uiContextReference);
491 appInstanceController.setAppComponentProvider(appComponentProvider);
492
493 final Map<Key, Object> initialScopedInstances = new HashMap<>();
494 initialScopedInstances.put(Key.get(AppContext.class), appInstanceController);
495 initialScopedInstances.put(Key.get(AppDescriptor.class), appInstanceController.getAppDescriptor());
496
497 SessionStore.access().createBeanStore(uiContextReference, initialScopedInstances);
498
499 return appComponentProvider;
500 }
501
502 private boolean locationIsEmpty(Location location) {
503 return Objects.equals(Location.NOWHERE, location) || (StringUtils.equals(Location.NOWHERE.getAppType(), location.getAppType())
504 && StringUtils.equals(Location.NOWHERE.getAppName(), location.getAppName())
505 && StringUtils.equals(Location.NOWHERE.getSubAppId(), location.getSubAppId())
506 && StringUtils.equals(Location.NOWHERE.getParameter(), location.getParameter()));
507 }
508
509 private boolean isAllowedToUser(final Location location) {
510 if (Location.LOCATION_TYPE_SHELL_APP.equals(location.getAppType())) {
511 return true;
512 }
513 final AppDescriptor appDescriptor = getAppDescriptor(location.getAppName());
514 final AccessDefinition permissions = appDescriptor.getPermissions();
515
516 return appDescriptor.isEnabled() && (permissions == null || permissions.hasAccess(user));
517 }
518 }