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