X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=swt%2Forg.argeo.app.swt%2Fsrc%2Forg%2Fargeo%2Fapp%2Fswt%2Fux%2FSwtArgeoApp.java;h=505c52d92ebdf52bd65291b30330edff12139fa1;hb=846518809f5f6a2e5c28e851d743ece803d95f6b;hp=0b6c34d33e38f53d17d496eb7fef07c5023b29ac;hpb=618968cf9d259ccded45a9455a26c516dbfe828f;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 0b6c34d..505c52d 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,14 +2,18 @@ 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.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; @@ -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; @@ -44,6 +47,8 @@ import org.argeo.cms.util.LangUtils; import org.argeo.cms.ux.CmsUxUtils; import org.argeo.eclipse.ui.specific.UiContext; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.widgets.Composite; import org.osgi.framework.Constants; @@ -55,17 +60,22 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber public final static String DEFAULT_UI_NAME_PROPERTY = "defaultUiName"; public final static String DEFAULT_THEME_ID_PROPERTY = "defaultThemeId"; public final static String DEFAULT_LAYER_PROPERTY = "defaultLayer"; + public final static String SHARED_PID_PREFIX_PROPERTY = "sharedPidPrefix"; + private final static String LOGIN = "login"; private final static String HOME_STATE = "~"; private String publicBasePath = null; + private String appPid; private String pidPrefix; - private String headerPid; - private String footerPid; - private String leadPanePid; - private String adminLeadPanePid; - private String loginScreenPid; + private String sharedPidPrefix; + +// private String headerPid; +// private String footerPid; +// private String leadPanePid; +// private String adminLeadPanePid; +// private String loginScreenPid; private String defaultUiName = "app"; private String adminUiName = "admin"; @@ -85,7 +95,8 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber // private CmsUserManager cmsUserManager; // TODO make more optimal or via CmsSession/CmsView - private Map managedUis = new HashMap<>(); + private static Timer janitorTimer = new Timer(true); + private Map> managedUis = new HashMap<>(); // ACR private ContentRepository contentRepository; @@ -93,44 +104,85 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber // JCR // private Repository repository; - public void init(Map properties) { + public void start(Map properties) { for (SuiteUxEvent event : SuiteUxEvent.values()) { 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)) defaultThemeId = LangUtils.get(properties, DEFAULT_THEME_ID_PROPERTY); if (properties.containsKey(DEFAULT_LAYER_PROPERTY)) defaultLayerPid = LangUtils.get(properties, DEFAULT_LAYER_PROPERTY); + sharedPidPrefix = LangUtils.get(properties, SHARED_PID_PREFIX_PROPERTY); publicBasePath = LangUtils.get(properties, PUBLIC_BASE_PATH_PROPERTY); if (properties.containsKey(Constants.SERVICE_PID)) { - String servicePid = properties.get(Constants.SERVICE_PID).toString(); - if (servicePid.endsWith(".app")) { - pidPrefix = servicePid.substring(0, servicePid.length() - "app".length()); + appPid = properties.get(Constants.SERVICE_PID).toString(); + int lastDotIndex = appPid.lastIndexOf('.'); + if (lastDotIndex >= 0) { + pidPrefix = appPid.substring(0, lastDotIndex); } + } else { + // 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 + janitorTimer.schedule(new TimerTask() { + + @Override + public void run() { + try { + Iterator>> uiRefs = 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"); + uiRefs.remove(); + 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 in " + appPid + ", removing it"); + uiRefs.remove(); + } + } + if (log.isTraceEnabled()) + log.trace(managedUis.size() + " UIs being managed by app " + appPid); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 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 destroy(Map properties) { - for (SwtAppUi ui : managedUis.values()) - if (!ui.isDisposed()) { + public void stop(Map properties) { + 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"); @@ -156,13 +208,11 @@ 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 String uid = cmsView.getUid(); - managedUis.put(uid, argeoSuiteUi); - argeoSuiteUi.addDisposeListener((e) -> { - managedUis.remove(uid); - if (log.isDebugEnabled()) - log.debug("Suite UI " + uid + " has been disposed."); - }); + argeoSuiteUi.addDisposeListener(new CleanUpUi(uid)); + managedUis.put(uid, new WeakReference<>(argeoSuiteUi)); return argeoSuiteUi; } @@ -179,6 +229,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) @@ -187,13 +238,13 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber ProvidedSession contentSession = (ProvidedSession) CmsUxUtils.getContentSession(contentRepository, cmsView); - SwtUiProvider headerUiProvider = findUiProvider(headerPid); - SwtUiProvider footerUiProvider = findUiProvider(footerPid); + SwtUiProvider headerUiProvider = findStructuralUiProvider(SwtAppUi.Structural.header.name()); + SwtUiProvider footerUiProvider = findStructuralUiProvider(SwtAppUi.Structural.footer.name()); SwtUiProvider leadPaneUiProvider; if (adminUiName.equals(uiName)) { - leadPaneUiProvider = findUiProvider(adminLeadPanePid); + leadPaneUiProvider = findStructuralUiProvider(SwtAppUi.Structural.adminLeadPane.name()); } else { - leadPaneUiProvider = findUiProvider(leadPanePid); + leadPaneUiProvider = findStructuralUiProvider(SwtAppUi.Structural.leadPane.name()); } Localized appTitle = null; @@ -208,7 +259,8 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber if (headerUiProvider != null) refreshPart(headerUiProvider, ui.getHeader(), context); ui.refreshBelowHeader(false); - refreshPart(findUiProvider(loginScreenPid), ui.getBelowHeader(), context); + SwtUiProvider loginScreenUiProvider = findStructuralUiProvider(SwtAppUi.Structural.loginScreen.name()); + refreshPart(loginScreenUiProvider, ui.getBelowHeader(), context); if (footerUiProvider != null) refreshPart(footerUiProvider, ui.getFooter(), context); ui.layout(true, true); @@ -225,10 +277,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); @@ -278,6 +330,17 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber uiProvider.createUiPart(part, context); } + private SwtUiProvider findStructuralUiProvider(String suffix) { + SwtUiProvider res = null; + if (pidPrefix != null) + res = findUiProvider(pidPrefix + "." + suffix); + if (res != null) + return res; + if (sharedPidPrefix != null) + res = findUiProvider(sharedPidPrefix + "." + suffix); + return res; + } + private SwtUiProvider findUiProvider(String pid) { if (!uiProvidersByPid.containsKey(pid)) return null; @@ -434,7 +497,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(); } @@ -462,11 +525,12 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber SwtAppUi ui = getRelatedUi(event); if (ui == null) return; + ui.updateLastAccess(); ui.getCmsView().runAs(() -> { try { String appTitle = ""; if (ui.getTitle() != null) - appTitle = ui.getTitle().lead() + " - "; + appTitle = ui.getTitle().lead(); if (isTopic(topic, SuiteUxEvent.refreshPart)) { Content node = getContentFromEvent(ui, event); @@ -476,7 +540,7 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber SwtAppLayer layer = findByType(layersByType, node); ui.switchToLayer(layer, node); layer.view(uiProvider, ui.getCurrentWorkArea(), node); - ui.getCmsView().stateChanged(nodeToState(node), appTitle + CmsUxUtils.getTitle(node)); + ui.getCmsView().stateChanged(nodeToState(node), stateTitle(appTitle, CmsUxUtils.getTitle(node))); } else if (isTopic(topic, SuiteUxEvent.openNewPart)) { Content node = getContentFromEvent(ui, event); if (node == null) @@ -485,7 +549,7 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber SwtAppLayer layer = findByType(layersByType, node); ui.switchToLayer(layer, node); layer.open(uiProvider, ui.getCurrentWorkArea(), node); - ui.getCmsView().stateChanged(nodeToState(node), appTitle + CmsUxUtils.getTitle(node)); + ui.getCmsView().stateChanged(nodeToState(node), stateTitle(appTitle, CmsUxUtils.getTitle(node))); } else if (isTopic(topic, SuiteUxEvent.switchLayer)) { String layerId = get(event, SuiteUxEvent.LAYER); if (layerId != null) { @@ -502,17 +566,17 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber if (nodeFromState != null && nodeFromState.getPath().equals(ui.getUserDir().getPath())) { // default layer view is forced String state = defaultLayerPid.equals(layerId) ? "~" : layerId; - ui.getCmsView().stateChanged(state, appTitle + title); + ui.getCmsView().stateChanged(state, stateTitle(appTitle, title)); suiteLayer.view(null, workArea, nodeFromState); } else { Content layerCurrentContext = suiteLayer.getCurrentContext(workArea); if (layerCurrentContext != null && !layerCurrentContext.equals(ui.getUserDir())) { // layer was already showing a context so we set the state to it ui.getCmsView().stateChanged(nodeToState(layerCurrentContext), - appTitle + CmsUxUtils.getTitle(layerCurrentContext)); + stateTitle(appTitle, CmsUxUtils.getTitle(layerCurrentContext))); } else { // no context was shown - ui.getCmsView().stateChanged(layerId, appTitle + title); + ui.getCmsView().stateChanged(layerId, stateTitle(appTitle, title)); } } } else { @@ -530,6 +594,10 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber }); } + private String stateTitle(String appTitle, String additionalTitle) { + return additionalTitle == null ? appTitle : appTitle + " - " + additionalTitle; + } + private boolean isTopic(String topic, CmsEvent cmsEvent) { Objects.requireNonNull(topic); return topic.equals(cmsEvent.topic()); @@ -561,7 +629,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) { @@ -643,14 +714,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; } @@ -659,4 +722,26 @@ public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber this.appUserState = appUserState; } + /** + * Dedicated class to clean up the UI in order to avoid illegal access issues + * with lambdas. + */ + private class CleanUpUi implements DisposeListener { + private static final long serialVersionUID = 1905900302262082463L; + final String uid; + + public CleanUpUi(String uid) { + this.uid = uid; + } + + @Override + public void widgetDisposed(DisposeEvent e) { + managedUis.remove(uid); + if (log.isDebugEnabled()) + log.debug("App " + appPid + " - Suite UI " + uid + " has been disposed (" + managedUis.size() + + " UIs still being managed)."); + } + + } + }