X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=swt%2Forg.argeo.app.swt%2Fsrc%2Forg%2Fargeo%2Fapp%2Fswt%2Fux%2FSwtArgeoApp.java;h=28641638d7f93a0ffdae1419153d5f0ecdce8374;hb=1fac2689d2c064f870a9f7f0f014f4dc7cc499d0;hp=80bd3552b4346e7bb52748fec83b7d7714decb75;hpb=ad42f2e927cb1eea17db610e76e13f0ac11b6080;p=gpl%2Fargeo-suite.git diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtArgeoApp.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtArgeoApp.java index 80bd355..2864163 100644 --- a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtArgeoApp.java +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/SwtArgeoApp.java @@ -2,16 +2,20 @@ package org.argeo.app.swt.ux; import static org.argeo.api.cms.ux.CmsView.CMS_VIEW_UID_PROPERTY; +import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; import java.util.TreeMap; -import java.util.TreeSet; import javax.xml.namespace.QName; @@ -36,7 +40,6 @@ import org.argeo.app.ux.AppUi; import org.argeo.app.ux.SuiteUxEvent; import org.argeo.cms.LocaleUtils; import org.argeo.cms.Localized; -import org.argeo.cms.acr.ContentUtils; import org.argeo.cms.swt.CmsSwtUtils; import org.argeo.cms.swt.acr.SwtUiProvider; import org.argeo.cms.swt.dialogs.CmsFeedback; @@ -92,7 +95,8 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber // private CmsUserManager cmsUserManager; // TODO make more optimal or via CmsSession/CmsView - private Map managedUis = Collections.synchronizedMap(new HashMap<>()); + private static Timer janitorTimer = new Timer(true); + private Map> managedUis = new HashMap<>(); // ACR private ContentRepository contentRepository; @@ -105,9 +109,6 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber getCmsContext().getCmsEventBus().addEventSubscriber(event.topic(), this); } - if (log.isDebugEnabled()) - log.info("Argeo Suite App started"); - if (properties.containsKey(DEFAULT_UI_NAME_PROPERTY)) defaultUiName = LangUtils.get(properties, DEFAULT_UI_NAME_PROPERTY); if (properties.containsKey(DEFAULT_THEME_ID_PROPERTY)) @@ -124,25 +125,66 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber pidPrefix = appPid.substring(0, lastDotIndex); } } else { - // TODO doe it make sense to accept that? + // TODO does it make sense to accept that? appPid = ""; } + Objects.requireNonNull(contentRepository, "Content repository must be provided"); + Objects.requireNonNull(appUserState, "App user state must be provided"); + + long janitorPeriod = 60 * 60 * 1000;// 1h + // long janitorPeriod = 60 * 1000;// min + janitorTimer.schedule(new TimerTask() { + + @Override + public void run() { + try { + // copy Map in order to avoid concurrent modification exception + Iterator>> uiRefs = new HashMap<>(managedUis).entrySet() + .iterator(); + refs: while (uiRefs.hasNext()) { + Map.Entry> entry = uiRefs.next(); + String uiUuid = entry.getKey(); + WeakReference uiRef = entry.getValue(); + SwtAppUi ui = uiRef.get(); + if (ui == null) { + if (log.isTraceEnabled()) + log.warn("Unreferenced UI " + uiUuid + " in " + appPid + ", removing it"); + managedUis.remove(uiUuid); + continue refs; + } + if (!ui.isDisposed() && !ui.getDisplay().isDisposed()) { + if (ui.isTimedOut()) { + if (log.isTraceEnabled()) + log.trace("Killing timed-out UI " + uiUuid + " in " + appPid); + UiContext.killDisplay(ui.getDisplay()); + } + } else { + if (log.isTraceEnabled()) + log.warn("Disposed UI " + uiUuid + " still referenced in " + appPid + ", removing it"); + managedUis.remove(uiUuid); + } + } + if (log.isTraceEnabled()) + log.trace(managedUis.size() + " UIs being managed by app " + appPid); + } catch (Exception e) { + log.error("Could not clean up timed-out UIs", e); + } + } + }, janitorPeriod, janitorPeriod); -// if (pidPrefix == null) -// throw new IllegalArgumentException("PID prefix must be set."); - -// headerPid = pidPrefix + "header"; -// footerPid = pidPrefix + "footer"; -// leadPanePid = pidPrefix + "leadPane"; -// adminLeadPanePid = pidPrefix + "adminLeadPane"; -// loginScreenPid = pidPrefix + "loginScreen"; + if (log.isDebugEnabled()) + log.info("Argeo Suite App " + appPid + " started"); } public void stop(Map properties) { - for (SwtAppUi ui : managedUis.values()) + refs: for (WeakReference uiRef : managedUis.values()) { + SwtAppUi ui = uiRef.get(); + if (ui == null) + continue refs; if (!ui.isDisposed() && !ui.getDisplay().isDisposed()) { ui.getDisplay().syncExec(() -> ui.dispose()); } + } managedUis.clear(); if (log.isDebugEnabled()) log.info("Argeo Suite App stopped"); @@ -169,21 +211,12 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber if (theme != null) CmsSwtUtils.registerCmsTheme(uiParent.getShell(), theme); SwtAppUi argeoSuiteUi = new SwtAppUi(uiParent, SWT.INHERIT_DEFAULT); + // TODO make timeout configurable + argeoSuiteUi.setUiTimeout(6 * 60 * 60 * 1000);// 6 hours + // argeoSuiteUi.setUiTimeout(60 * 1000);// 1 min String uid = cmsView.getUid(); - managedUis.put(uid, argeoSuiteUi); argeoSuiteUi.addDisposeListener(new CleanUpUi(uid)); -// argeoSuiteUi.addDisposeListener((e) -> { -// managedUis.remove(uid); -// if (log.isDebugEnabled()) -// log.debug("Suite UI " + uid + " has been disposed."); -// }); -// Display.getCurrent().disposeExec(() -> { -// if (managedUis.containsKey(uid)) { -// managedUis.remove(uid); -// if (log.isDebugEnabled()) -// log.debug("Suite UI " + uid + " has been disposed from Display#disposeExec()."); -// } -// }); + managedUis.put(uid, new WeakReference<>(argeoSuiteUi)); return argeoSuiteUi; } @@ -200,6 +233,7 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber try { Content context = null; SwtAppUi ui = (SwtAppUi) cmsUi; + ui.updateLastAccess(); String uiName = Objects.toString(ui.getParent().getData(UI_NAME_PROPERTY), null); if (uiName == null) @@ -247,10 +281,10 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber if (cmsSession == null || cmsView.isAnonymous()) { assert publicBasePath != null; Content userDir = contentSession - .get(ContentUtils.SLASH + CmsConstants.SYS_WORKSPACE + publicBasePath); + .get(Content.ROOT_PATH + CmsConstants.SYS_WORKSPACE + publicBasePath); ui.setUserDir(userDir); } else { - Content userDir = appUserState.getOrCreateSessionDir(contentSession, cmsSession); + Content userDir = appUserState.getOrCreateSessionDir(cmsSession); ui.setUserDir(userDir); // Node userDirNode = jcrContentProvider.doInAdminSession((adminSession) -> { // Node node = SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession); @@ -323,65 +357,10 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber return layersByPid.get(pid).get(); } - private T findByType(Map> byType, Content content) { + private List listTypes(Map byType, Content content) { if (content == null) - throw new IllegalArgumentException("A node should be provided"); - -// boolean checkJcr = false; -// if (checkJcr && content instanceof JcrContent) { -// Node context = ((JcrContent) content).getJcrNode(); -// try { -// // mixins -// Set types = new TreeSet<>(); -// for (NodeType mixinType : context.getMixinNodeTypes()) { -// String mixinTypeName = mixinType.getName(); -// if (byType.containsKey(mixinTypeName)) { -// types.add(mixinTypeName); -// } -// for (NodeType superType : mixinType.getDeclaredSupertypes()) { -// if (byType.containsKey(superType.getName())) { -// types.add(superType.getName()); -// } -// } -// } -// // primary node type -// NodeType primaryType = context.getPrimaryNodeType(); -// String primaryTypeName = primaryType.getName(); -// if (byType.containsKey(primaryTypeName)) { -// types.add(primaryTypeName); -// } -// for (NodeType superType : primaryType.getDeclaredSupertypes()) { -// if (byType.containsKey(superType.getName())) { -// types.add(superType.getName()); -// } -// } -// // entity type -// if (context.isNodeType(EntityType.entity.get())) { -// if (context.hasProperty(EntityNames.ENTITY_TYPE)) { -// String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString(); -// if (byType.containsKey(entityTypeName)) { -// types.add(entityTypeName); -// } -// } -// } -// -// if (CmsJcrUtils.isUserHome(context) && byType.containsKey("nt:folder")) {// home node -// types.add("nt:folder"); -// } -// -// if (types.size() == 0) -// throw new IllegalArgumentException( -// "No type found for " + context + " (" + listTypes(context) + ")"); -// String type = types.iterator().next(); -// if (!byType.containsKey(type)) -// throw new IllegalArgumentException("No component found for " + context + " with type " + type); -// return byType.get(type).get(); -// } catch (RepositoryException e) { -// throw new IllegalStateException(e); -// } -// -// } else { - Set types = new TreeSet<>(); + throw new IllegalArgumentException("A content should be provided"); + List types = new ArrayList<>(); if (content.hasContentClass(EntityType.entity.qName())) { String type = content.attr(EntityName.type.qName()); if (type != null && byType.containsKey(type)) @@ -394,40 +373,33 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber if (byType.containsKey(type)) types.add(type); } - if (types.size() == 0) { + if (types.isEmpty()) throw new IllegalArgumentException("No type found for " + content + " (" + objectClasses + ")"); + return types; + } + + private RankedObject findLayerByType(Content content) { + List types = listTypes(layersByType, content); + // we assume the types will be ordered by priority + // (no possible for LDAP at this stage) + for (String type : types) { + if (layersByType.containsKey(type)) + return layersByType.get(type); } - String type = types.iterator().next(); - if (!byType.containsKey(type)) - throw new IllegalArgumentException("No component found for " + content + " with type " + type); - return byType.get(type).get(); -// } + throw new IllegalArgumentException("No layer found for " + content + " with type " + types); } -// private static String listTypes(Node context) { -// try { -// StringBuilder sb = new StringBuilder(); -// sb.append(context.getPrimaryNodeType().getName()); -// for (NodeType superType : context.getPrimaryNodeType().getDeclaredSupertypes()) { -// sb.append(' '); -// sb.append(superType.getName()); -// } -// -// for (NodeType nodeType : context.getMixinNodeTypes()) { -// sb.append(' '); -// sb.append(nodeType.getName()); -// if (nodeType.getName().equals(EntityType.local.get())) -// sb.append('/').append(context.getProperty(EntityNames.ENTITY_TYPE).getString()); -// for (NodeType superType : nodeType.getDeclaredSupertypes()) { -// sb.append(' '); -// sb.append(superType.getName()); -// } -// } -// return sb.toString(); -// } catch (RepositoryException e) { -// throw new JcrException(e); -// } -// } + private RankedObject findUiProviderByType(Content content) { + RankedObject layerRO = findLayerByType(content); + List layerTypes = LangUtils.toStringList(layerRO.getProperties().get(EntityConstants.TYPE)); + List types = listTypes(uiProvidersByType, content); + // layer types are ordered by priority + for (String type : layerTypes) { + if (types.contains(type) && uiProvidersByType.containsKey(type)) + return uiProvidersByType.get(type); + } + throw new IllegalArgumentException("No UI provider found for " + content + " with types " + types); + } @Override public void setState(CmsUi cmsUi, String state) { @@ -435,8 +407,6 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber if (state == null) return; if (!state.startsWith("/")) { -// if (cmsUi instanceof SwtAppUi) { -// SwtAppUi ui = (SwtAppUi) cmsUi; if (LOGIN.equals(state)) { String appTitle = ""; if (ui.getTitle() != null) @@ -449,10 +419,8 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber properties.put(SuiteUxEvent.LAYER, layerId); properties.put(SuiteUxEvent.CONTENT_PATH, HOME_STATE); ui.getCmsView().sendEvent(SuiteUxEvent.switchLayer.topic(), properties); -// } return; } -// SwtAppUi suiteUi = (SwtAppUi) cmsUi; if (ui.isLoginScreen()) { return; } @@ -467,7 +435,7 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber } // TODO move it to an internal package? - public static String nodeToState(Content node) { + private static String nodeToState(Content node) { return node.getPath(); } @@ -495,6 +463,7 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber SwtAppUi ui = getRelatedUi(event); if (ui == null) return; + ui.updateLastAccess(); ui.getCmsView().runAs(() -> { try { String appTitle = ""; @@ -502,26 +471,28 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber appTitle = ui.getTitle().lead(); if (isTopic(topic, SuiteUxEvent.refreshPart)) { - Content node = getContentFromEvent(ui, event); - if (node == null) + Content content = getContentFromEvent(ui, event); + if (content == null) return; - SwtUiProvider uiProvider = findByType(uiProvidersByType, node); - SwtAppLayer layer = findByType(layersByType, node); - ui.switchToLayer(layer, node); - layer.view(uiProvider, ui.getCurrentWorkArea(), node); - ui.getCmsView().stateChanged(nodeToState(node), stateTitle(appTitle, CmsUxUtils.getTitle(node))); + SwtUiProvider uiProvider = findUiProviderByType(content).get(); + SwtAppLayer layer = findLayerByType(content).get(); + ui.switchToLayer(layer, content); + layer.view(uiProvider, ui.getCurrentWorkArea(), content); + ui.getCmsView().stateChanged(nodeToState(content), + stateTitle(appTitle, CmsUxUtils.getTitle(content))); } else if (isTopic(topic, SuiteUxEvent.openNewPart)) { - Content node = getContentFromEvent(ui, event); - if (node == null) + Content content = getContentFromEvent(ui, event); + if (content == null) return; - SwtUiProvider uiProvider = findByType(uiProvidersByType, node); - SwtAppLayer layer = findByType(layersByType, node); - ui.switchToLayer(layer, node); - layer.open(uiProvider, ui.getCurrentWorkArea(), node); - ui.getCmsView().stateChanged(nodeToState(node), stateTitle(appTitle, CmsUxUtils.getTitle(node))); + SwtUiProvider uiProvider = findUiProviderByType(content).get(); + SwtAppLayer layer = findLayerByType(content).get(); + ui.switchToLayer(layer, content); + layer.open(uiProvider, ui.getCurrentWorkArea(), content); + ui.getCmsView().stateChanged(nodeToState(content), + stateTitle(appTitle, CmsUxUtils.getTitle(content))); } else if (isTopic(topic, SuiteUxEvent.switchLayer)) { String layerId = get(event, SuiteUxEvent.LAYER); - if (layerId != null) { + if (layerId != null && !"".equals(layerId.trim())) { SwtAppLayer suiteLayer = findLayer(layerId); if (suiteLayer == null) throw new IllegalArgumentException("No layer '" + layerId + "' available."); @@ -549,10 +520,10 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber } } } else { - Content node = getContentFromEvent(ui, event); - if (node != null) { - SwtAppLayer layer = findByType(layersByType, node); - ui.switchToLayer(layer, node); + Content content = getContentFromEvent(ui, event); + if (content != null) { + SwtAppLayer layer = findLayerByType(content).get(); + ui.switchToLayer(layer, content); } } } @@ -583,14 +554,6 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber Content node; if (path == null) { return null; -// // look for a user -// String username = get(event, SuiteUxEvent.USERNAME); -// if (username == null) -// return null; -// User user = cmsUserManager.getUser(username); -// if (user == null) -// return null; -// node = ContentUtils.roleToContent(cmsUserManager, contentSession, user); } else { node = contentSession.get(path); } @@ -598,7 +561,10 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber } private SwtAppUi getRelatedUi(Map eventProperties) { - return managedUis.get(get(eventProperties, CMS_VIEW_UID_PROPERTY)); + WeakReference uiRef = managedUis.get(get(eventProperties, CMS_VIEW_UID_PROPERTY)); + if (uiRef == null) + return null; + return uiRef.get(); } public static String get(Map eventProperties, String key) { @@ -653,6 +619,7 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber RankedObject.putIfHigherRank(layersByPid, pid, layer, properties); } if (properties.containsKey(EntityConstants.TYPE)) { + // TODO check consistency of entity types with overridden ? List types = LangUtils.toStringList(properties.get(EntityConstants.TYPE)); for (String type : types) RankedObject.putIfHigherRank(layersByType, type, layer, properties); @@ -680,14 +647,6 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber } } -// public void setCmsUserManager(CmsUserManager cmsUserManager) { -// this.cmsUserManager = cmsUserManager; -// } - -// protected ContentRepository getContentRepository() { -// return contentRepository; -// } - public void setContentRepository(ContentRepository contentRepository) { this.contentRepository = contentRepository; } @@ -705,7 +664,6 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber final String uid; public CleanUpUi(String uid) { - super(); this.uid = uid; }