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