]> git.argeo.org Git - gpl/argeo-suite.git/blob - swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtArgeoApp.java
Merge tag 'v2.3.23' into testing
[gpl/argeo-suite.git] / swt / org.argeo.app.swt / src / org / argeo / app / swt / ux / SwtArgeoApp.java
1 package org.argeo.app.swt.ux;
2
3 import static org.argeo.api.cms.ux.CmsView.CMS_VIEW_UID_PROPERTY;
4
5 import java.lang.ref.WeakReference;
6 import java.util.Collections;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.Locale;
12 import java.util.Map;
13 import java.util.Objects;
14 import java.util.Set;
15 import java.util.Timer;
16 import java.util.TimerTask;
17 import java.util.TreeMap;
18 import java.util.TreeSet;
19
20 import javax.xml.namespace.QName;
21
22 import org.argeo.api.acr.Content;
23 import org.argeo.api.acr.ContentRepository;
24 import org.argeo.api.acr.spi.ProvidedSession;
25 import org.argeo.api.cms.CmsConstants;
26 import org.argeo.api.cms.CmsEvent;
27 import org.argeo.api.cms.CmsEventSubscriber;
28 import org.argeo.api.cms.CmsLog;
29 import org.argeo.api.cms.CmsSession;
30 import org.argeo.api.cms.ux.CmsTheme;
31 import org.argeo.api.cms.ux.CmsUi;
32 import org.argeo.api.cms.ux.CmsView;
33 import org.argeo.app.api.AppUserState;
34 import org.argeo.app.api.EntityConstants;
35 import org.argeo.app.api.EntityName;
36 import org.argeo.app.api.EntityType;
37 import org.argeo.app.api.RankedObject;
38 import org.argeo.app.ux.AbstractArgeoApp;
39 import org.argeo.app.ux.AppUi;
40 import org.argeo.app.ux.SuiteUxEvent;
41 import org.argeo.cms.LocaleUtils;
42 import org.argeo.cms.Localized;
43 import org.argeo.cms.swt.CmsSwtUtils;
44 import org.argeo.cms.swt.acr.SwtUiProvider;
45 import org.argeo.cms.swt.dialogs.CmsFeedback;
46 import org.argeo.cms.util.LangUtils;
47 import org.argeo.cms.ux.CmsUxUtils;
48 import org.argeo.eclipse.ui.specific.UiContext;
49 import org.eclipse.swt.SWT;
50 import org.eclipse.swt.events.DisposeEvent;
51 import org.eclipse.swt.events.DisposeListener;
52 import org.eclipse.swt.widgets.Composite;
53 import org.osgi.framework.Constants;
54
55 /** The Argeo Suite App. */
56 public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber {
57 private final static CmsLog log = CmsLog.getLog(SwtArgeoApp.class);
58
59 public final static String PUBLIC_BASE_PATH_PROPERTY = "publicBasePath";
60 public final static String DEFAULT_UI_NAME_PROPERTY = "defaultUiName";
61 public final static String DEFAULT_THEME_ID_PROPERTY = "defaultThemeId";
62 public final static String DEFAULT_LAYER_PROPERTY = "defaultLayer";
63 public final static String SHARED_PID_PREFIX_PROPERTY = "sharedPidPrefix";
64
65 private final static String LOGIN = "login";
66 private final static String HOME_STATE = "~";
67
68 private String publicBasePath = null;
69
70 private String appPid;
71 private String pidPrefix;
72 private String sharedPidPrefix;
73
74 // private String headerPid;
75 // private String footerPid;
76 // private String leadPanePid;
77 // private String adminLeadPanePid;
78 // private String loginScreenPid;
79
80 private String defaultUiName = "app";
81 private String adminUiName = "admin";
82
83 // FIXME such default names make refactoring more dangerous
84 @Deprecated
85 private String defaultLayerPid = "argeo.suite.ui.dashboardLayer";
86 @Deprecated
87 private String defaultThemeId = "org.argeo.app.theme.default";
88
89 // TODO use QName as key for byType
90 private Map<String, RankedObject<SwtUiProvider>> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>());
91 private Map<String, RankedObject<SwtUiProvider>> uiProvidersByType = Collections.synchronizedMap(new HashMap<>());
92 private Map<String, RankedObject<SwtAppLayer>> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>());
93 private Map<String, RankedObject<SwtAppLayer>> layersByType = Collections.synchronizedSortedMap(new TreeMap<>());
94
95 // private CmsUserManager cmsUserManager;
96
97 // TODO make more optimal or via CmsSession/CmsView
98 private static Timer janitorTimer = new Timer(true);
99 private Map<String, WeakReference<SwtAppUi>> managedUis = new HashMap<>();
100
101 // ACR
102 private ContentRepository contentRepository;
103 private AppUserState appUserState;
104 // JCR
105 // private Repository repository;
106
107 public void start(Map<String, Object> properties) {
108 for (SuiteUxEvent event : SuiteUxEvent.values()) {
109 getCmsContext().getCmsEventBus().addEventSubscriber(event.topic(), this);
110 }
111
112 if (properties.containsKey(DEFAULT_UI_NAME_PROPERTY))
113 defaultUiName = LangUtils.get(properties, DEFAULT_UI_NAME_PROPERTY);
114 if (properties.containsKey(DEFAULT_THEME_ID_PROPERTY))
115 defaultThemeId = LangUtils.get(properties, DEFAULT_THEME_ID_PROPERTY);
116 if (properties.containsKey(DEFAULT_LAYER_PROPERTY))
117 defaultLayerPid = LangUtils.get(properties, DEFAULT_LAYER_PROPERTY);
118 sharedPidPrefix = LangUtils.get(properties, SHARED_PID_PREFIX_PROPERTY);
119 publicBasePath = LangUtils.get(properties, PUBLIC_BASE_PATH_PROPERTY);
120
121 if (properties.containsKey(Constants.SERVICE_PID)) {
122 appPid = properties.get(Constants.SERVICE_PID).toString();
123 int lastDotIndex = appPid.lastIndexOf('.');
124 if (lastDotIndex >= 0) {
125 pidPrefix = appPid.substring(0, lastDotIndex);
126 }
127 } else {
128 // TODO does it make sense to accept that?
129 appPid = "<unknown>";
130 }
131 Objects.requireNonNull(contentRepository, "Content repository must be provided");
132 Objects.requireNonNull(appUserState, "App user state must be provided");
133
134 long janitorPeriod = 60 * 60 * 1000;// 1h
135 janitorTimer.schedule(new TimerTask() {
136
137 @Override
138 public void run() {
139 try {
140 Iterator<Map.Entry<String, WeakReference<SwtAppUi>>> uiRefs = managedUis.entrySet().iterator();
141 refs: while (uiRefs.hasNext()) {
142 Map.Entry<String, WeakReference<SwtAppUi>> entry = uiRefs.next();
143 String uiUuid = entry.getKey();
144 WeakReference<SwtAppUi> uiRef = entry.getValue();
145 SwtAppUi ui = uiRef.get();
146 if (ui == null) {
147 if (log.isTraceEnabled())
148 log.warn("Unreferenced UI " + uiUuid + " in " + appPid + ", removing it");
149 uiRefs.remove();
150 continue refs;
151 }
152 if (!ui.isDisposed() && !ui.getDisplay().isDisposed()) {
153 if (ui.isTimedOut()) {
154 if (log.isTraceEnabled())
155 log.trace("Killing timed-out UI " + uiUuid + " in " + appPid);
156 UiContext.killDisplay(ui.getDisplay());
157 }
158 } else {
159 if (log.isTraceEnabled())
160 log.warn("Disposed UI " + uiUuid + " still in " + appPid + ", removing it");
161 uiRefs.remove();
162 }
163 }
164 if (log.isTraceEnabled())
165 log.trace(managedUis.size() + " UIs being managed by app " + appPid);
166 } catch (Exception e) {
167 e.printStackTrace();
168 }
169 }
170 }, janitorPeriod, janitorPeriod);
171
172 if (log.isDebugEnabled())
173 log.info("Argeo Suite App " + appPid + " started");
174 }
175
176 public void stop(Map<String, Object> properties) {
177 refs: for (WeakReference<SwtAppUi> uiRef : managedUis.values()) {
178 SwtAppUi ui = uiRef.get();
179 if (ui == null)
180 continue refs;
181 if (!ui.isDisposed() && !ui.getDisplay().isDisposed()) {
182 ui.getDisplay().syncExec(() -> ui.dispose());
183 }
184 }
185 managedUis.clear();
186 if (log.isDebugEnabled())
187 log.info("Argeo Suite App stopped");
188
189 }
190
191 @Override
192 public Set<String> getUiNames() {
193 HashSet<String> uiNames = new HashSet<>();
194 uiNames.add(defaultUiName);
195 uiNames.add(adminUiName);
196 return uiNames;
197 }
198
199 @Override
200 public CmsUi initUi(Object parent) {
201 Composite uiParent = (Composite) parent;
202 String uiName = uiParent.getData(UI_NAME_PROPERTY) != null ? uiParent.getData(UI_NAME_PROPERTY).toString()
203 : null;
204 CmsView cmsView = CmsSwtUtils.getCmsView(uiParent);
205 if (cmsView == null)
206 throw new IllegalStateException("No CMS view is registered.");
207 CmsTheme theme = getTheme(uiName);
208 if (theme != null)
209 CmsSwtUtils.registerCmsTheme(uiParent.getShell(), theme);
210 SwtAppUi argeoSuiteUi = new SwtAppUi(uiParent, SWT.INHERIT_DEFAULT);
211 // TODO make timeout configurable
212 argeoSuiteUi.setUiTimeout(6 * 60 * 60 * 1000);// 6 hours
213 String uid = cmsView.getUid();
214 argeoSuiteUi.addDisposeListener(new CleanUpUi(uid));
215 managedUis.put(uid, new WeakReference<>(argeoSuiteUi));
216 return argeoSuiteUi;
217 }
218
219 @Override
220 public String getThemeId(String uiName) {
221 String themeId = System.getProperty("org.argeo.app.theme.default");
222 if (themeId != null)
223 return themeId;
224 return defaultThemeId;
225 }
226
227 @Override
228 public void refreshUi(CmsUi cmsUi, String state) {
229 try {
230 Content context = null;
231 SwtAppUi ui = (SwtAppUi) cmsUi;
232 ui.updateLastAccess();
233
234 String uiName = Objects.toString(ui.getParent().getData(UI_NAME_PROPERTY), null);
235 if (uiName == null)
236 throw new IllegalStateException("UI name should not be null");
237 CmsView cmsView = CmsSwtUtils.getCmsView(ui);
238
239 ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository, cmsView);
240
241 SwtUiProvider headerUiProvider = findStructuralUiProvider(SwtAppUi.Structural.header.name());
242 SwtUiProvider footerUiProvider = findStructuralUiProvider(SwtAppUi.Structural.footer.name());
243 SwtUiProvider leadPaneUiProvider;
244 if (adminUiName.equals(uiName)) {
245 leadPaneUiProvider = findStructuralUiProvider(SwtAppUi.Structural.adminLeadPane.name());
246 } else {
247 leadPaneUiProvider = findStructuralUiProvider(SwtAppUi.Structural.leadPane.name());
248 }
249
250 Localized appTitle = null;
251 if (headerUiProvider instanceof DefaultHeader) {
252 appTitle = ((DefaultHeader) headerUiProvider).getTitle();
253 }
254 ui.setTitle(appTitle);
255
256 if (cmsView.isAnonymous() && publicBasePath == null) {// internal app, must login
257 ui.logout();
258 ui.setLoginScreen(true);
259 if (headerUiProvider != null)
260 refreshPart(headerUiProvider, ui.getHeader(), context);
261 ui.refreshBelowHeader(false);
262 SwtUiProvider loginScreenUiProvider = findStructuralUiProvider(SwtAppUi.Structural.loginScreen.name());
263 refreshPart(loginScreenUiProvider, ui.getBelowHeader(), context);
264 if (footerUiProvider != null)
265 refreshPart(footerUiProvider, ui.getFooter(), context);
266 ui.layout(true, true);
267 setState(ui, LOGIN);
268 } else {
269 if (LOGIN.equals(state))
270 state = null;
271 if (ui.isLoginScreen()) {
272 ui.setLoginScreen(false);
273 }
274 CmsSession cmsSession = cmsView.getCmsSession();
275 if (ui.getUserDir() == null) {
276 // FIXME NPE on CMSSession when logging in from anonymous
277 if (cmsSession == null || cmsView.isAnonymous()) {
278 assert publicBasePath != null;
279 Content userDir = contentSession
280 .get(Content.ROOT_PATH + CmsConstants.SYS_WORKSPACE + publicBasePath);
281 ui.setUserDir(userDir);
282 } else {
283 Content userDir = appUserState.getOrCreateSessionDir(cmsSession);
284 ui.setUserDir(userDir);
285 // Node userDirNode = jcrContentProvider.doInAdminSession((adminSession) -> {
286 // Node node = SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
287 // return node;
288 // });
289 // Content userDir = contentSession
290 // .get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + userDirNode.getPath());
291 // ui.setUserDir(userDir);
292 }
293 }
294 initLocale(cmsSession);
295 context = stateToNode(ui, state);
296 if (context == null)
297 context = ui.getUserDir();
298
299 if (headerUiProvider != null)
300 refreshPart(headerUiProvider, ui.getHeader(), context);
301 ui.refreshBelowHeader(true);
302 for (String key : layersByPid.keySet()) {
303 SwtAppLayer layer = layersByPid.get(key).get();
304 ui.addLayer(key, layer);
305 }
306
307 if (leadPaneUiProvider != null)
308 refreshPart(leadPaneUiProvider, ui.getLeadPane(), context);
309 if (footerUiProvider != null)
310 refreshPart(footerUiProvider, ui.getFooter(), context);
311 ui.layout(true, true);
312 setState(ui, state != null ? state : defaultLayerPid);
313 }
314 } catch (Exception e) {
315 CmsFeedback.error("Unexpected exception", e);
316 }
317 }
318
319 private void initLocale(CmsSession cmsSession) {
320 if (cmsSession == null)
321 return;
322 Locale locale = cmsSession.getLocale();
323 UiContext.setLocale(locale);
324 LocaleUtils.setThreadLocale(locale);
325
326 }
327
328 private void refreshPart(SwtUiProvider uiProvider, Composite part, Content context) {
329 CmsSwtUtils.clear(part);
330 uiProvider.createUiPart(part, context);
331 }
332
333 private SwtUiProvider findStructuralUiProvider(String suffix) {
334 SwtUiProvider res = null;
335 if (pidPrefix != null)
336 res = findUiProvider(pidPrefix + "." + suffix);
337 if (res != null)
338 return res;
339 if (sharedPidPrefix != null)
340 res = findUiProvider(sharedPidPrefix + "." + suffix);
341 return res;
342 }
343
344 private SwtUiProvider findUiProvider(String pid) {
345 if (!uiProvidersByPid.containsKey(pid))
346 return null;
347 return uiProvidersByPid.get(pid).get();
348 }
349
350 private SwtAppLayer findLayer(String pid) {
351 if (!layersByPid.containsKey(pid))
352 return null;
353 return layersByPid.get(pid).get();
354 }
355
356 private <T> T findByType(Map<String, RankedObject<T>> byType, Content content) {
357 if (content == null)
358 throw new IllegalArgumentException("A node should be provided");
359
360 // boolean checkJcr = false;
361 // if (checkJcr && content instanceof JcrContent) {
362 // Node context = ((JcrContent) content).getJcrNode();
363 // try {
364 // // mixins
365 // Set<String> types = new TreeSet<>();
366 // for (NodeType mixinType : context.getMixinNodeTypes()) {
367 // String mixinTypeName = mixinType.getName();
368 // if (byType.containsKey(mixinTypeName)) {
369 // types.add(mixinTypeName);
370 // }
371 // for (NodeType superType : mixinType.getDeclaredSupertypes()) {
372 // if (byType.containsKey(superType.getName())) {
373 // types.add(superType.getName());
374 // }
375 // }
376 // }
377 // // primary node type
378 // NodeType primaryType = context.getPrimaryNodeType();
379 // String primaryTypeName = primaryType.getName();
380 // if (byType.containsKey(primaryTypeName)) {
381 // types.add(primaryTypeName);
382 // }
383 // for (NodeType superType : primaryType.getDeclaredSupertypes()) {
384 // if (byType.containsKey(superType.getName())) {
385 // types.add(superType.getName());
386 // }
387 // }
388 // // entity type
389 // if (context.isNodeType(EntityType.entity.get())) {
390 // if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
391 // String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
392 // if (byType.containsKey(entityTypeName)) {
393 // types.add(entityTypeName);
394 // }
395 // }
396 // }
397 //
398 // if (CmsJcrUtils.isUserHome(context) && byType.containsKey("nt:folder")) {// home node
399 // types.add("nt:folder");
400 // }
401 //
402 // if (types.size() == 0)
403 // throw new IllegalArgumentException(
404 // "No type found for " + context + " (" + listTypes(context) + ")");
405 // String type = types.iterator().next();
406 // if (!byType.containsKey(type))
407 // throw new IllegalArgumentException("No component found for " + context + " with type " + type);
408 // return byType.get(type).get();
409 // } catch (RepositoryException e) {
410 // throw new IllegalStateException(e);
411 // }
412 //
413 // } else {
414 Set<String> types = new TreeSet<>();
415 if (content.hasContentClass(EntityType.entity.qName())) {
416 String type = content.attr(EntityName.type.qName());
417 if (type != null && byType.containsKey(type))
418 types.add(type);
419 }
420
421 List<QName> objectClasses = content.getContentClasses();
422 for (QName cc : objectClasses) {
423 String type = cc.getPrefix() + ":" + cc.getLocalPart();
424 if (byType.containsKey(type))
425 types.add(type);
426 }
427 if (types.size() == 0) {
428 throw new IllegalArgumentException("No type found for " + content + " (" + objectClasses + ")");
429 }
430 String type = types.iterator().next();
431 if (!byType.containsKey(type))
432 throw new IllegalArgumentException("No component found for " + content + " with type " + type);
433 return byType.get(type).get();
434 // }
435 }
436
437 // private static String listTypes(Node context) {
438 // try {
439 // StringBuilder sb = new StringBuilder();
440 // sb.append(context.getPrimaryNodeType().getName());
441 // for (NodeType superType : context.getPrimaryNodeType().getDeclaredSupertypes()) {
442 // sb.append(' ');
443 // sb.append(superType.getName());
444 // }
445 //
446 // for (NodeType nodeType : context.getMixinNodeTypes()) {
447 // sb.append(' ');
448 // sb.append(nodeType.getName());
449 // if (nodeType.getName().equals(EntityType.local.get()))
450 // sb.append('/').append(context.getProperty(EntityNames.ENTITY_TYPE).getString());
451 // for (NodeType superType : nodeType.getDeclaredSupertypes()) {
452 // sb.append(' ');
453 // sb.append(superType.getName());
454 // }
455 // }
456 // return sb.toString();
457 // } catch (RepositoryException e) {
458 // throw new JcrException(e);
459 // }
460 // }
461
462 @Override
463 public void setState(CmsUi cmsUi, String state) {
464 AppUi ui = (AppUi) cmsUi;
465 if (state == null)
466 return;
467 if (!state.startsWith("/")) {
468 // if (cmsUi instanceof SwtAppUi) {
469 // SwtAppUi ui = (SwtAppUi) cmsUi;
470 if (LOGIN.equals(state)) {
471 String appTitle = "";
472 if (ui.getTitle() != null)
473 appTitle = ui.getTitle().lead();
474 ui.getCmsView().stateChanged(state, appTitle);
475 return;
476 }
477 Map<String, Object> properties = new HashMap<>();
478 String layerId = HOME_STATE.equals(state) ? defaultLayerPid : state;
479 properties.put(SuiteUxEvent.LAYER, layerId);
480 properties.put(SuiteUxEvent.CONTENT_PATH, HOME_STATE);
481 ui.getCmsView().sendEvent(SuiteUxEvent.switchLayer.topic(), properties);
482 // }
483 return;
484 }
485 // SwtAppUi suiteUi = (SwtAppUi) cmsUi;
486 if (ui.isLoginScreen()) {
487 return;
488 }
489
490 Content node = stateToNode(ui, state);
491 if (node == null) {
492 ui.getCmsView().navigateTo(HOME_STATE);
493 } else {
494 ui.getCmsView().sendEvent(SuiteUxEvent.switchLayer.topic(), SuiteUxEvent.eventProperties(node));
495 ui.getCmsView().sendEvent(SuiteUxEvent.refreshPart.topic(), SuiteUxEvent.eventProperties(node));
496 }
497 }
498
499 // TODO move it to an internal package?
500 private static String nodeToState(Content node) {
501 return node.getPath();
502 }
503
504 private Content stateToNode(CmsUi suiteUi, String state) {
505 if (suiteUi == null)
506 return null;
507 if (state == null || !state.startsWith("/"))
508 return null;
509
510 String path = state;
511
512 ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository,
513 suiteUi.getCmsView());
514 return contentSession.get(path);
515 }
516
517 /*
518 * Events management
519 */
520
521 @Override
522 public void onEvent(String topic, Map<String, Object> event) {
523
524 // Specific UI related events
525 SwtAppUi ui = getRelatedUi(event);
526 if (ui == null)
527 return;
528 ui.updateLastAccess();
529 ui.getCmsView().runAs(() -> {
530 try {
531 String appTitle = "";
532 if (ui.getTitle() != null)
533 appTitle = ui.getTitle().lead();
534
535 if (isTopic(topic, SuiteUxEvent.refreshPart)) {
536 Content node = getContentFromEvent(ui, event);
537 if (node == null)
538 return;
539 SwtUiProvider uiProvider = findByType(uiProvidersByType, node);
540 SwtAppLayer layer = findByType(layersByType, node);
541 ui.switchToLayer(layer, node);
542 layer.view(uiProvider, ui.getCurrentWorkArea(), node);
543 ui.getCmsView().stateChanged(nodeToState(node), stateTitle(appTitle, CmsUxUtils.getTitle(node)));
544 } else if (isTopic(topic, SuiteUxEvent.openNewPart)) {
545 Content node = getContentFromEvent(ui, event);
546 if (node == null)
547 return;
548 SwtUiProvider uiProvider = findByType(uiProvidersByType, node);
549 SwtAppLayer layer = findByType(layersByType, node);
550 ui.switchToLayer(layer, node);
551 layer.open(uiProvider, ui.getCurrentWorkArea(), node);
552 ui.getCmsView().stateChanged(nodeToState(node), stateTitle(appTitle, CmsUxUtils.getTitle(node)));
553 } else if (isTopic(topic, SuiteUxEvent.switchLayer)) {
554 String layerId = get(event, SuiteUxEvent.LAYER);
555 if (layerId != null) {
556 SwtAppLayer suiteLayer = findLayer(layerId);
557 if (suiteLayer == null)
558 throw new IllegalArgumentException("No layer '" + layerId + "' available.");
559 Localized layerTitle = suiteLayer.getTitle();
560 // FIXME make sure we don't rebuild the work area twice
561 Composite workArea = ui.switchToLayer(layerId, ui.getUserDir());
562 String title = null;
563 if (layerTitle != null)
564 title = layerTitle.lead();
565 Content nodeFromState = getContentFromEvent(ui, event);
566 if (nodeFromState != null && nodeFromState.getPath().equals(ui.getUserDir().getPath())) {
567 // default layer view is forced
568 String state = defaultLayerPid.equals(layerId) ? "~" : layerId;
569 ui.getCmsView().stateChanged(state, stateTitle(appTitle, title));
570 suiteLayer.view(null, workArea, nodeFromState);
571 } else {
572 Content layerCurrentContext = suiteLayer.getCurrentContext(workArea);
573 if (layerCurrentContext != null && !layerCurrentContext.equals(ui.getUserDir())) {
574 // layer was already showing a context so we set the state to it
575 ui.getCmsView().stateChanged(nodeToState(layerCurrentContext),
576 stateTitle(appTitle, CmsUxUtils.getTitle(layerCurrentContext)));
577 } else {
578 // no context was shown
579 ui.getCmsView().stateChanged(layerId, stateTitle(appTitle, title));
580 }
581 }
582 } else {
583 Content node = getContentFromEvent(ui, event);
584 if (node != null) {
585 SwtAppLayer layer = findByType(layersByType, node);
586 ui.switchToLayer(layer, node);
587 }
588 }
589 }
590 } catch (Exception e) {
591 CmsFeedback.error("Cannot handle event " + topic + " " + event, e);
592 // log.error("Cannot handle event " + event, e);
593 }
594 });
595 }
596
597 private String stateTitle(String appTitle, String additionalTitle) {
598 return additionalTitle == null ? appTitle : appTitle + " - " + additionalTitle;
599 }
600
601 private boolean isTopic(String topic, CmsEvent cmsEvent) {
602 Objects.requireNonNull(topic);
603 return topic.equals(cmsEvent.topic());
604 }
605
606 protected Content getContentFromEvent(SwtAppUi ui, Map<String, Object> event) {
607 ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository,
608 ui.getCmsView());
609
610 String path = get(event, SuiteUxEvent.CONTENT_PATH);
611
612 if (path != null && (path.equals(HOME_STATE) || path.equals("")))
613 return ui.getUserDir();
614 Content node;
615 if (path == null) {
616 return null;
617 // // look for a user
618 // String username = get(event, SuiteUxEvent.USERNAME);
619 // if (username == null)
620 // return null;
621 // User user = cmsUserManager.getUser(username);
622 // if (user == null)
623 // return null;
624 // node = ContentUtils.roleToContent(cmsUserManager, contentSession, user);
625 } else {
626 node = contentSession.get(path);
627 }
628 return node;
629 }
630
631 private SwtAppUi getRelatedUi(Map<String, Object> eventProperties) {
632 WeakReference<SwtAppUi> uiRef = managedUis.get(get(eventProperties, CMS_VIEW_UID_PROPERTY));
633 if (uiRef == null)
634 return null;
635 return uiRef.get();
636 }
637
638 public static String get(Map<String, Object> eventProperties, String key) {
639 Object value = eventProperties.get(key);
640 if (value == null)
641 return null;
642 return value.toString();
643
644 }
645
646 /*
647 * Dependency injection.
648 */
649
650 public void addUiProvider(SwtUiProvider uiProvider, Map<String, Object> properties) {
651 if (properties.containsKey(Constants.SERVICE_PID)) {
652 String pid = (String) properties.get(Constants.SERVICE_PID);
653 RankedObject.putIfHigherRank(uiProvidersByPid, pid, uiProvider, properties);
654 }
655 if (properties.containsKey(EntityConstants.TYPE)) {
656 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
657 for (String type : types) {
658 RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties);
659 }
660 }
661 }
662
663 public void removeUiProvider(SwtUiProvider uiProvider, Map<String, Object> properties) {
664 if (properties.containsKey(Constants.SERVICE_PID)) {
665 String pid = (String) properties.get(Constants.SERVICE_PID);
666 if (uiProvidersByPid.containsKey(pid)) {
667 if (uiProvidersByPid.get(pid).equals(new RankedObject<SwtUiProvider>(uiProvider, properties))) {
668 uiProvidersByPid.remove(pid);
669 }
670 }
671 }
672 if (properties.containsKey(EntityConstants.TYPE)) {
673 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
674 for (String type : types) {
675 if (uiProvidersByType.containsKey(type)) {
676 if (uiProvidersByType.get(type).equals(new RankedObject<SwtUiProvider>(uiProvider, properties))) {
677 uiProvidersByType.remove(type);
678 }
679 }
680 }
681 }
682 }
683
684 public void addLayer(SwtAppLayer layer, Map<String, Object> properties) {
685 if (properties.containsKey(Constants.SERVICE_PID)) {
686 String pid = (String) properties.get(Constants.SERVICE_PID);
687 RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
688 }
689 if (properties.containsKey(EntityConstants.TYPE)) {
690 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
691 for (String type : types)
692 RankedObject.putIfHigherRank(layersByType, type, layer, properties);
693 }
694 }
695
696 public void removeLayer(SwtAppLayer layer, Map<String, Object> properties) {
697 if (properties.containsKey(Constants.SERVICE_PID)) {
698 String pid = (String) properties.get(Constants.SERVICE_PID);
699 if (layersByPid.containsKey(pid)) {
700 if (layersByPid.get(pid).equals(new RankedObject<SwtAppLayer>(layer, properties))) {
701 layersByPid.remove(pid);
702 }
703 }
704 }
705 if (properties.containsKey(EntityConstants.TYPE)) {
706 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
707 for (String type : types) {
708 if (layersByType.containsKey(type)) {
709 if (layersByType.get(type).equals(new RankedObject<SwtAppLayer>(layer, properties))) {
710 layersByType.remove(type);
711 }
712 }
713 }
714 }
715 }
716
717 public void setContentRepository(ContentRepository contentRepository) {
718 this.contentRepository = contentRepository;
719 }
720
721 public void setAppUserState(AppUserState appUserState) {
722 this.appUserState = appUserState;
723 }
724
725 /**
726 * Dedicated class to clean up the UI in order to avoid illegal access issues
727 * with lambdas.
728 */
729 private class CleanUpUi implements DisposeListener {
730 private static final long serialVersionUID = 1905900302262082463L;
731 final String uid;
732
733 public CleanUpUi(String uid) {
734 this.uid = uid;
735 }
736
737 @Override
738 public void widgetDisposed(DisposeEvent e) {
739 managedUis.remove(uid);
740 if (log.isDebugEnabled())
741 log.debug("App " + appPid + " - Suite UI " + uid + " has been disposed (" + managedUis.size()
742 + " UIs still being managed).");
743 }
744
745 }
746
747 }