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 final AppInstanceController appInstanceController = appHistory.peekFirst();
195 if (appInstanceController != null) {
196 doStop(appInstanceController);
197 }
198 }
199
200 @Override
201 public boolean isAppStarted(String appName) {
202 return runningApps.containsKey(appName);
203 }
204
205 @Override
206 public void focusCurrentApp() {
207 if (currentAppInstanceController != null) {
208 doFocus(currentAppInstanceController);
209 }
210 }
211
212 @Override
213 public App getCurrentApp() {
214 return currentAppInstanceController == null ? null : currentAppInstanceController.getApp();
215 }
216
217
218
219
220
221
222 @Override
223 public Location getCurrentAppLocation() {
224 return currentAppInstanceController == null ? null : currentAppInstanceController.getCurrentLocation();
225 }
226
227
228
229
230 @Override
231 public Location getAppLocation(String appName) {
232 AppInstanceController appInstanceController = runningApps.get(appName);
233 return appInstanceController == null ? null : appInstanceController.getCurrentLocation();
234 }
235
236
237
238
239
240 private AppInstanceController doStartIfNotAlreadyRunning(AppInstanceController appInstanceController, Location location) {
241 if (isAppStarted(appInstanceController.getAppDescriptor().getName())) {
242 appInstanceController.onLocationUpdate(location);
243 sendEvent(AppLifecycleEventType.FOCUSED, appInstanceController.getAppDescriptor());
244 return appInstanceController;
245 }
246
247 runningApps.put(appInstanceController.getAppDescriptor().getName(), appInstanceController);
248 appInstanceController.start(location);
249 sendEvent(AppLifecycleEventType.STARTED, appInstanceController.getAppDescriptor());
250 return appInstanceController;
251 }
252
253
254
255
256
257 private void doFocus(AppInstanceController appInstanceController) {
258 locationController.goTo(appInstanceController.getCurrentLocation());
259
260
261 }
262
263 private void doStop(AppInstanceController appInstanceController) {
264 final Location currentLocation = locationController.getWhere();
265
266 sendEvent(AppLifecycleEventType.STOPPED, appInstanceController.getAppDescriptor());
267 appInstanceController.stop();
268 while (appHistory.remove(appInstanceController)) {
269 ;
270 }
271
272 final String appName = appInstanceController.getAppDescriptor().getName();
273 runningApps.remove(appName);
274 appInstances.remove(appName);
275
276 if (currentAppInstanceController == appInstanceController) {
277 currentAppInstanceController = null;
278 viewport.setView(null);
279 }
280
281 if (!appHistory.isEmpty()) {
282 doFocus(appHistory.peekFirst());
283 } else if (!AdmincentralFlavour.get().isM5()) {
284
285 locationController.goTo(new DefaultLocation("home"));
286 }
287
288 if (AdmincentralFlavour.get().isM5() &&
289 StringUtils.equalsIgnoreCase(appName, currentLocation.getAppName())) {
290
291 locationController.goTo(new DefaultLocation(Location.LOCATION_TYPE_SHELL_APP, "applauncher"));
292 }
293 }
294
295 private void sendEvent(AppLifecycleEventType appEventType, AppDescriptor appDescriptor) {
296 eventBus.fireEvent(new AppLifecycleEvent(appDescriptor, appEventType));
297 }
298
299
300
301
302
303
304
305
306
307
308
309
310 @Override
311 public void onLocationChanged(LocationChangedEvent event) {
312 Location newLocation = event.getNewLocation();
313
314
315
316 String actualParam = newLocation.getParameter();
317 if (StringUtils.isEmpty(actualParam) && newLocation instanceof DefaultLocation) {
318 Location lastAppLocation = getAppLocation(newLocation.getAppName());
319 if (lastAppLocation != null) {
320 ((DefaultLocation) newLocation).setParameter(lastAppLocation.getParameter());
321 }
322 }
323
324 if (locationIsEmpty(newLocation)) {
325 return;
326 }
327
328 if (!newLocation.getAppType().equals(Location.LOCATION_TYPE_APP)) {
329 return;
330 }
331
332 if (newLocation.equals(getCurrentAppLocation())) {
333 return;
334 }
335
336 if (!isAllowedToUser(newLocation)) {
337 return;
338 }
339
340 AppDescriptor nextApp = getAppForLocation(newLocation);
341 if (nextApp == null) {
342 return;
343 }
344
345 AppInstanceController nextAppContext = getAppInstance(nextApp.getName());
346
347
348 Location updateLocation = updateLocation(nextAppContext, newLocation);
349 if (!updateLocation.equals(newLocation)) {
350 locationController.goTo(updateLocation);
351 return;
352 }
353
354 if (currentAppInstanceController != nextAppContext) {
355 appHistory.addFirst(nextAppContext);
356 currentAppInstanceController = nextAppContext;
357 }
358
359 nextAppContext = doStartIfNotAlreadyRunning(nextAppContext, newLocation);
360
361 try {
362 viewport.setView(nextAppContext.getApp().getView());
363 } catch (Exception e) {
364 log.error("App {} failed to start: {}", nextApp.getName(), e.getMessage(), e);
365 App failedApp = componentProvider.newInstance(FailedAppStub.class, nextAppContext, e);
366 failedApp.start(newLocation);
367 viewport.setView(failedApp.getView());
368 }
369 }
370
371
372
373
374
375
376
377
378 private Location updateLocation(AppInstanceController appInstanceController, Location location) {
379 String appType = location.getAppType();
380 String appName = location.getAppName();
381 String subAppId = location.getSubAppId();
382 String params = location.getParameter();
383
384 if (StringUtils.isBlank(subAppId)) {
385
386 if (isAppStarted(appName)) {
387 AppInstanceController runningAppContext = runningApps.get(appName);
388 subAppId = runningAppContext.getCurrentLocation().getSubAppId();
389 } else if (StringUtils.isBlank(subAppId)) {
390 Location defaultLocation = appInstanceController.getDefaultLocation();
391 if (defaultLocation != null) {
392 subAppId = defaultLocation.getSubAppId();
393 } else {
394 log.warn("No default location could be found for the '{}' app, please check subapp configuration.", appName);
395 }
396
397 }
398 }
399
400 return new DefaultLocation(appType, appName, subAppId, params);
401 }
402
403 private AppInstanceController getAppInstance(String appName) {
404 return appInstances.computeIfAbsent(appName, this::createNewAppInstance);
405 }
406
407 private AppInstanceController createNewAppInstance(String appName) {
408 AppDescriptor descriptor = getAppDescriptor(appName);
409 if (descriptor == null) {
410 return null;
411 }
412
413 AppInstanceController appInstanceController = componentProvider.newInstance(AppInstanceController.class, descriptor);
414 createAppComponentProvider(appInstanceController);
415
416 return appInstanceController;
417 }
418
419 @Override
420 public void onLocationChangeRequested(LocationChangeRequestedEvent event) {
421 if (currentAppInstanceController != null) {
422 final String message = currentAppInstanceController.mayStop();
423 if (message != null) {
424 event.setWarning(message);
425 }
426 }
427 }
428
429 @Override
430 public void openChooseDialog(String appName, UiContext uiContext, String selectedId, ChooseDialogCallback callback) {
431 openChooseDialog(appName, uiContext, null, selectedId, callback);
432 }
433
434 @Override
435 public void openChooseDialog(String appName, UiContext uiContext, String targetTreeRootPath, String selectedId, ChooseDialogCallback callback) {
436 App targetApp = getAppWithoutStarting(appName);
437 if (targetApp != null) {
438 final ChooseDialogCallback composedCallback = ChooseDialogCallback.composeWith(targetApp::stop, callback);
439 if (StringUtils.isNotBlank(targetTreeRootPath)) {
440 targetApp.openChooseDialog(uiContext, targetTreeRootPath, selectedId, composedCallback);
441 } else {
442 targetApp.openChooseDialog(uiContext, selectedId, callback);
443 }
444 }
445 }
446
447 private AppDescriptor getAppForLocation(Location location) {
448 return getAppDescriptor(location.getAppName());
449 }
450
451 private AppDescriptor getAppDescriptor(String name) throws RuntimeException {
452 final DefinitionProvider<AppDescriptor> definitionProvider;
453 try {
454 definitionProvider = appDescriptorRegistry.getProvider(name);
455 } catch (Registry.NoSuchDefinitionException | IllegalStateException e) {
456 Message errorMessage = new Message();
457 errorMessage.setType(MessageType.ERROR);
458 errorMessage.setSubject(i18n.translate("ui-framework.app.appdescriptorReadError.subject"));
459 errorMessage.setMessage(String.format(i18n.translate("ui-framework.app.appdescriptorReadError.message"), name));
460 messagesManager.sendLocalMessage(errorMessage);
461 throw new RuntimeException(e);
462 }
463 return definitionProvider.get();
464 }
465
466 private ComponentProvider createAppComponentProvider(AppInstanceController appInstanceController) {
467 final UiContextReference uiContextReference = UiContextReference.ofApp(appInstanceController);
468 final UiContextBoundComponentProvider appComponentProvider = new UiContextBoundComponentProvider(uiContextReference);
469 appInstanceController.setAppComponentProvider(appComponentProvider);
470
471 final Map<Key, Object> initialScopedInstances = new HashMap<>();
472 initialScopedInstances.put(Key.get(AppContext.class), appInstanceController);
473
474 SessionStore.access().createBeanStore(uiContextReference, initialScopedInstances);
475
476 return appComponentProvider;
477 }
478
479 private boolean locationIsEmpty(Location location) {
480 return Objects.equals(Location.NOWHERE, location) || (StringUtils.equals(Location.NOWHERE.getAppType(), location.getAppType())
481 && StringUtils.equals(Location.NOWHERE.getAppName(), location.getAppName())
482 && StringUtils.equals(Location.NOWHERE.getSubAppId(), location.getSubAppId())
483 && StringUtils.equals(Location.NOWHERE.getParameter(), location.getParameter()));
484 }
485
486 private boolean isAllowedToUser(final Location location) {
487 if (Location.LOCATION_TYPE_SHELL_APP.equals(location.getAppType())) {
488 return true;
489 }
490 final AppDescriptor appDescriptor = getAppDescriptor(location.getAppName());
491 final AccessDefinition permissions = appDescriptor.getPermissions();
492
493 return appDescriptor.isEnabled() && (permissions == null || permissions.hasAccess(user));
494 }
495 }