+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.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.SwtUiProvider;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+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;
+
+/** The Argeo Suite App. */
+public class SwtArgeoApp extends AbstractArgeoApp implements CmsEventSubscriber {
+ private final static CmsLog log = CmsLog.getLog(SwtArgeoApp.class);
+
+ public final static String PUBLIC_BASE_PATH_PROPERTY = "publicBasePath";
+ 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 sharedPidPrefix;
+
+// private String headerPid;
+// private String footerPid;
+// private String leadPanePid;
+// private String adminLeadPanePid;
+// private String loginScreenPid;
+
+ private String defaultUiName = "app";
+ private String adminUiName = "admin";
+
+ // FIXME such default names make refactoring more dangerous
+ @Deprecated
+ private String defaultLayerPid = "argeo.suite.ui.dashboardLayer";
+ @Deprecated
+ private String defaultThemeId = "org.argeo.app.theme.default";
+
+ // TODO use QName as key for byType
+ private Map<String, RankedObject<SwtUiProvider>> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>());
+ private Map<String, RankedObject<SwtUiProvider>> uiProvidersByType = Collections.synchronizedMap(new HashMap<>());
+ private Map<String, RankedObject<SwtAppLayer>> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>());
+ private Map<String, RankedObject<SwtAppLayer>> layersByType = Collections.synchronizedSortedMap(new TreeMap<>());
+
+// private CmsUserManager cmsUserManager;
+
+ // TODO make more optimal or via CmsSession/CmsView
+ private static Timer janitorTimer = new Timer(true);
+ private Map<String, WeakReference<SwtAppUi>> managedUis = new HashMap<>();
+
+ // ACR
+ private ContentRepository contentRepository;
+ private AppUserState appUserState;
+ // JCR
+// private Repository repository;
+
+ public void start(Map<String, Object> properties) {
+ for (SuiteUxEvent event : SuiteUxEvent.values()) {
+ getCmsContext().getCmsEventBus().addEventSubscriber(event.topic(), this);
+ }
+
+ 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)) {
+ 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 = "<unknown>";
+ }
+ 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<Map.Entry<String, WeakReference<SwtAppUi>>> uiRefs = new HashMap<>(managedUis).entrySet()
+ .iterator();
+ refs: while (uiRefs.hasNext()) {
+ Map.Entry<String, WeakReference<SwtAppUi>> entry = uiRefs.next();
+ String uiUuid = entry.getKey();
+ WeakReference<SwtAppUi> 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 (log.isDebugEnabled())
+ log.info("Argeo Suite App " + appPid + " started");
+ }
+
+ public void stop(Map<String, Object> properties) {
+ refs: for (WeakReference<SwtAppUi> 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");
+
+ }