Merge tag 'v2.3.27' into testing
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 19 Dec 2023 06:07:16 +0000 (07:07 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 19 Dec 2023 06:07:16 +0000 (07:07 +0100)
52 files changed:
Makefile
Makefile-rcp.mk
org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java [deleted file]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java [deleted file]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java [deleted file]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java [deleted file]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java [deleted file]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java [deleted file]
org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java [deleted file]
org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java [deleted file]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java [deleted file]
org.argeo.cms.jshell/src/org/argeo/cms/jshell/CmsJShell.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java
org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java [deleted file]
org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java
org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java
org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java
org.argeo.init/src/org/argeo/init/Service.java
org.argeo.init/src/org/argeo/init/a2/A2Component.java
org.argeo.init/src/org/argeo/init/a2/FsA2Source.java
org.argeo.init/src/org/argeo/init/a2/OsgiContext.java
org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java
org.argeo.init/src/org/argeo/init/logging/ThinLogging.java
org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java
osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd
osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java
osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java
osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java
sdk/argeo-build
sdk/branches/unstable.bnd
sdk/cms-e4-rap.properties
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java [deleted file]
swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java
swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java

index ec06bd782e55106e6a994968ff0ff7e43d321d4a..9011b2dc0aa267d56969f17b19c082368e37144b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -36,6 +36,7 @@ DEP_CATEGORIES = \
 crypto/fips/org.argeo.tp.crypto \
 org.argeo.tp \
 org.argeo.tp.httpd \
+org.argeo.tp.sshd \
 osgi/equinox/org.argeo.tp.osgi \
 osgi/equinox/org.argeo.tp.eclipse \
 swt/rap/org.argeo.tp.swt \
index 2cadd3ae77c80c804b6c3788508e0b4019faa5ec..1fb93728c82474391695677a9bbce12c52a90d05 100644 (file)
@@ -13,7 +13,7 @@ DEP_CATEGORIES = \
 org.argeo.cms \
 swt/org.argeo.cms \
 org.argeo.tp \
-org.argeo.tp.jetty \
+org.argeo.tp.httpd \
 osgi/equinox/org.argeo.tp.eclipse \
 osgi/api/org.argeo.tp.osgi \
 swt/rcp/org.argeo.tp.swt \
index 96a09a91b1bfdb496cdf857a0ad4af3d2171aca0..3375bcde51e4f234f31e76b71cc747a1214fa1de 100644 (file)
@@ -235,7 +235,12 @@ public interface CmsLog {
 
        static CmsLog getLog(String name) {
                if (isSystemLoggerAvailable) {
-                       return new SystemCmsLog(name);
+                       SystemCmsLog systemCmsLog = new SystemCmsLog(name);
+                       if (systemCmsLog.logger == null) {
+                               System.err.println("System logger unexpectedly null for " + name + ", switching to fall back");
+                               return new FallBackCmsLog();
+                       }
+                       return systemCmsLog;
                } else { // typically Android
                        return new FallBackCmsLog();
                }
@@ -259,10 +264,11 @@ public interface CmsLog {
  * Java platform.
  */
 class SystemCmsLog implements CmsLog {
-       private final Logger logger;
+       final Logger logger;
 
        SystemCmsLog(String name) {
                logger = System.getLogger(name);
+               assert logger != null : "System logger should not be null";
        }
 
        @Override
index 2103e49894dcb17916dd75511dcc8acad48fc762..fc8c9e0bc1405d3c74f14a3932383232e1771537 100644 (file)
@@ -5,6 +5,13 @@ public interface CmsUi {
        Object getData(String key);
 
        void setData(String key, Object value);
-       
+
        CmsView getCmsView();
+
+       void updateLastAccess();
+
+       default boolean isTimedOut() {
+               return false;
+       };
+
 }
index 121e4bdcbf601a654fc608aeda89ec895f4b4e3b..b231bea9935dd2b478bb054134243243858b8cbb 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.api.cms.ux;
 import java.net.URI;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.TimerTask;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 
@@ -100,4 +101,10 @@ public interface CmsView {
                throw new UnsupportedOperationException();
        }
 
+       /** Schedule a one-shot UX task to be executed within the UX context/thread. */
+       TimerTask schedule(Runnable task, long delay);
+
+       /** Schedule a recurring UX task to be executed within the UX context/thread. */
+       TimerTask schedule(Runnable task, long delay, long period);
+
 }
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java
deleted file mode 100644 (file)
index 6727229..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.io.Writer;
-
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.util.ExceptionsChain;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/** Serialisable wrapper of a {@link Throwable}. */
-public class CmsExceptionsChain extends ExceptionsChain {
-       public final static CmsLog log = CmsLog.getLog(CmsExceptionsChain.class);
-
-       public CmsExceptionsChain() {
-               super();
-       }
-
-       public CmsExceptionsChain(Throwable exception) {
-               super(exception);
-               if (log.isDebugEnabled())
-                       log.error("Exception chain", exception);
-       }
-
-       public String toJsonString(ObjectMapper objectMapper) {
-               try {
-                       return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
-               } catch (JsonProcessingException e) {
-                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
-               }
-       }
-
-       public void writeAsJson(ObjectMapper objectMapper, Writer writer) {
-               try {
-                       JsonGenerator jg = objectMapper.writerWithDefaultPrettyPrinter().getFactory().createGenerator(writer);
-                       jg.writeObject(this);
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
-               }
-       }
-
-       public void writeAsJson(ObjectMapper objectMapper, HttpServletResponse resp) {
-               try {
-                       resp.setContentType("application/json");
-                       resp.setStatus(500);
-                       writeAsJson(objectMapper, resp.getWriter());
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
-               }
-       }
-
-//     public static void main(String[] args) throws Exception {
-//             try {
-//                     try {
-//                             try {
-//                                     testDeeper();
-//                             } catch (Exception e) {
-//                                     throw new Exception("Less deep exception", e);
-//                             }
-//                     } catch (Exception e) {
-//                             throw new RuntimeException("Top exception", e);
-//                     }
-//             } catch (Exception e) {
-//                     CmsExceptionsChain systemErrors = new CmsExceptionsChain(e);
-//                     ObjectMapper objectMapper = new ObjectMapper();
-//                     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(systemErrors));
-//                     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(e));
-//                     e.printStackTrace();
-//             }
-//     }
-//
-//     static void testDeeper() throws Exception {
-//             throw new IllegalStateException("Deep exception");
-//     }
-
-}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java
deleted file mode 100644 (file)
index 29a3137..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.util.Locale;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsSessionId;
-import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.osgi.service.useradmin.Authorization;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/** Externally authenticate an http session. */
-public class CmsLoginServlet extends HttpServlet {
-       public final static String PARAM_USERNAME = "username";
-       public final static String PARAM_PASSWORD = "password";
-
-       private static final long serialVersionUID = 2478080654328751539L;
-       private ObjectMapper objectMapper = new ObjectMapper();
-
-       @Override
-       protected void doGet(HttpServletRequest request, HttpServletResponse response)
-                       throws ServletException, IOException {
-               doPost(request, response);
-       }
-
-       @Override
-       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               LoginContext lc = null;
-               String username = req.getParameter(PARAM_USERNAME);
-               String password = req.getParameter(PARAM_PASSWORD);
-               ServletHttpRequest request = new ServletHttpRequest(req);
-               ServletHttpResponse response = new ServletHttpResponse(resp);
-               try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) {
-                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                       for (Callback callback : callbacks) {
-                                               if (callback instanceof NameCallback && username != null)
-                                                       ((NameCallback) callback).setName(username);
-                                               else if (callback instanceof PasswordCallback && password != null)
-                                                       ((PasswordCallback) callback).setPassword(password.toCharArray());
-                                               else if (callback instanceof RemoteAuthCallback) {
-                                                       ((RemoteAuthCallback) callback).setRequest(request);
-                                                       ((RemoteAuthCallback) callback).setResponse(response);
-                                               }
-                                       }
-                               }
-                       });
-                       lc.login();
-
-                       Subject subject = lc.getSubject();
-                       CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class));
-                       if (cmsSessionId == null) {
-                               resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-                               return;
-                       }
-                       Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class));
-                       Locale locale = extractFrom(subject.getPublicCredentials(Locale.class));
-
-                       CmsSessionDescriptor cmsSessionDescriptor = new CmsSessionDescriptor(authorization.getName(),
-                                       cmsSessionId.getUuid().toString(), authorization.getRoles(), authorization.toString(),
-                                       locale != null ? locale.toString() : null);
-
-                       resp.setContentType("application/json");
-                       JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter());
-                       jg.writeObject(cmsSessionDescriptor);
-
-                       String redirectTo = redirectTo(req);
-                       if (redirectTo != null)
-                               resp.sendRedirect(redirectTo);
-               } catch (LoginException e) {
-                       resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-                       return;
-               }
-       }
-
-       protected <T> T extractFrom(Set<T> creds) {
-               if (creds.size() > 0)
-                       return creds.iterator().next();
-               else
-                       return null;
-       }
-
-       /**
-        * To be overridden in order to return a richer {@link CmsSessionDescriptor} to
-        * be serialized.
-        */
-       protected CmsSessionDescriptor enrichJson(CmsSessionDescriptor cmsSessionDescriptor) {
-               return cmsSessionDescriptor;
-       }
-
-       protected String redirectTo(HttpServletRequest request) {
-               return null;
-       }
-}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java
deleted file mode 100644 (file)
index d18637d..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsSessionId;
-import org.argeo.cms.CurrentUser;
-import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-
-/** Externally authenticate an http session. */
-public class CmsLogoutServlet extends HttpServlet {
-       private static final long serialVersionUID = 2478080654328751539L;
-
-       @Override
-       protected void doGet(HttpServletRequest request, HttpServletResponse response)
-                       throws ServletException, IOException {
-               doPost(request, response);
-       }
-
-       @Override
-       protected void doPost(HttpServletRequest request, HttpServletResponse response)
-                       throws ServletException, IOException {
-               ServletHttpRequest httpRequest = new ServletHttpRequest(request);
-               ServletHttpResponse httpResponse = new ServletHttpResponse(response);
-               LoginContext lc = null;
-               try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
-                                       new RemoteAuthCallbackHandler(httpRequest, httpResponse) {
-                                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                                       for (Callback callback : callbacks) {
-                                                               if (callback instanceof RemoteAuthCallback) {
-                                                                       ((RemoteAuthCallback) callback).setRequest(httpRequest);
-                                                                       ((RemoteAuthCallback) callback).setResponse(httpResponse);
-                                                               }
-                                                       }
-                                               }
-                                       });
-                       lc.login();
-
-                       Subject subject = lc.getSubject();
-                       CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class));
-                       if (cmsSessionId != null) {// logged in
-                               CurrentUser.logoutCmsSession(subject);
-                       }
-
-               } catch (LoginException e) {
-                       // ignore
-               }
-
-               String redirectTo = redirectTo(request);
-               if (redirectTo != null)
-                       response.sendRedirect(redirectTo);
-       }
-
-       protected <T> T extractFrom(Set<T> creds) {
-               if (creds.size() > 0)
-                       return creds.iterator().next();
-               else
-                       return null;
-       }
-
-       protected String redirectTo(HttpServletRequest request) {
-               return null;
-       }
-}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java
deleted file mode 100644 (file)
index 09f17ae..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.security.AccessControlContext;
-import java.util.Map;
-
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.auth.RemoteAuthUtils;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.osgi.service.http.context.ServletContextHelper;
-
-/** Manages security access to servlets. */
-public class CmsPrivateServletContext extends ServletContextHelper {
-       public final static String LOGIN_PAGE = "argeo.cms.integration.loginPage";
-       public final static String LOGIN_SERVLET = "argeo.cms.integration.loginServlet";
-       private String loginPage;
-       private String loginServlet;
-
-       public void init(Map<String, String> properties) {
-               loginPage = properties.get(LOGIN_PAGE);
-               loginServlet = properties.get(LOGIN_SERVLET);
-       }
-
-       /**
-        * Add the {@link AccessControlContext} as a request attribute, or redirect to
-        * the login page.
-        */
-       @Override
-       public boolean handleSecurity(final HttpServletRequest req, HttpServletResponse resp) throws IOException {
-               LoginContext lc = null;
-               ServletHttpRequest request = new ServletHttpRequest(req);
-               ServletHttpResponse response = new ServletHttpResponse(resp);
-
-               String pathInfo = req.getPathInfo();
-               String servletPath = req.getServletPath();
-               if ((pathInfo != null && (servletPath + pathInfo).equals(loginPage)) || servletPath.contentEquals(loginServlet))
-                       return true;
-               try {
-                       lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(request, response));
-                       lc.login();
-               } catch (LoginException e) {
-                       lc = processUnauthorized(req, resp);
-                       if (lc == null)
-                               return false;
-               }
-//             Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
-//
-//                     @Override
-//                     public Void run() {
-//                             // TODO also set login context in order to log out ?
-//                             RemoteAuthUtils.configureRequestSecurity(request);
-//                             return null;
-//                     }
-//
-//             });
-
-               return true;
-       }
-
-//     @Override
-//     public void finishSecurity(HttpServletRequest req, HttpServletResponse resp) {
-//             RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(req));
-//     }
-
-       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
-               try {
-                       response.sendRedirect(loginPage);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot redirect to login page", e);
-               }
-               return null;
-       }
-}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java
deleted file mode 100644 (file)
index 30de616..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.argeo.api.cms.CmsSession;
-import org.osgi.service.useradmin.Authorization;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-/** A serializable descriptor of an internal {@link CmsSession}. */
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class CmsSessionDescriptor implements Serializable, Authorization {
-       private static final long serialVersionUID = 8592162323372641462L;
-
-       private String name;
-       private String cmsSessionId;
-       private String displayName;
-       private String locale;
-       private Set<String> roles;
-
-       public CmsSessionDescriptor() {
-       }
-
-       public CmsSessionDescriptor(String name, String cmsSessionId, String[] roles, String displayName, String locale) {
-               this.name = name;
-               this.displayName = displayName;
-               this.cmsSessionId = cmsSessionId;
-               this.locale = locale;
-               this.roles = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles)));
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public void setName(String name) {
-               this.name = name;
-       }
-
-       public String getDisplayName() {
-               return displayName;
-       }
-
-       public void setDisplayName(String displayName) {
-               this.displayName = displayName;
-       }
-
-       public String getCmsSessionId() {
-               return cmsSessionId;
-       }
-
-       public void setCmsSessionId(String cmsSessionId) {
-               this.cmsSessionId = cmsSessionId;
-       }
-
-       public Boolean isAnonymous() {
-               return name == null;
-       }
-
-       public String getLocale() {
-               return locale;
-       }
-
-       public void setLocale(String locale) {
-               this.locale = locale;
-       }
-
-       @Override
-       public boolean hasRole(String name) {
-               return roles.contains(name);
-       }
-
-       @Override
-       public String[] getRoles() {
-               return roles.toArray(new String[roles.size()]);
-       }
-
-       public void setRoles(String[] roles) {
-               this.roles = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles)));
-       }
-
-       @Override
-       public int hashCode() {
-               return cmsSessionId != null ? cmsSessionId.hashCode() : super.hashCode();
-       }
-
-       @Override
-       public String toString() {
-               return displayName != null ? displayName : name != null ? name : super.toString();
-       }
-
-}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java
deleted file mode 100644 (file)
index c355ecd..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.time.ZonedDateTime;
-import java.util.Set;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.acr.ldap.NamingUtils;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.directory.CmsUserManager;
-import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.osgi.service.useradmin.Authorization;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/** Provides access to tokens. */
-public class CmsTokenServlet extends HttpServlet {
-       private static final long serialVersionUID = 302918711430864140L;
-
-       public final static String PARAM_EXPIRY_DATE = "expiryDate";
-       public final static String PARAM_TOKEN = "token";
-
-       private final static int DEFAULT_HOURS = 24;
-
-       private CmsUserManager userManager;
-       private ObjectMapper objectMapper = new ObjectMapper();
-
-       @Override
-       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               ServletHttpRequest request = new ServletHttpRequest(req);
-               ServletHttpResponse response = new ServletHttpResponse(resp);
-               LoginContext lc = null;
-               try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) {
-                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                       for (Callback callback : callbacks) {
-                                               if (callback instanceof RemoteAuthCallback) {
-                                                       ((RemoteAuthCallback) callback).setRequest(request);
-                                                       ((RemoteAuthCallback) callback).setResponse(response);
-                                               }
-                                       }
-                               }
-                       });
-                       lc.login();
-               } catch (LoginException e) {
-                       // ignore
-               }
-
-               try {
-                       Subject subject = lc.getSubject();
-                       Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class));
-                       String token = UUID.randomUUID().toString();
-                       String expiryDateStr = req.getParameter(PARAM_EXPIRY_DATE);
-                       ZonedDateTime expiryDate;
-                       if (expiryDateStr != null) {
-                               expiryDate = NamingUtils.ldapDateToZonedDateTime(expiryDateStr);
-                       } else {
-                               expiryDate = ZonedDateTime.now().plusHours(DEFAULT_HOURS);
-                               expiryDateStr = NamingUtils.instantToLdapDate(expiryDate);
-                       }
-                       userManager.addAuthToken(authorization.getName(), token, expiryDate);
-
-                       TokenDescriptor tokenDescriptor = new TokenDescriptor();
-                       tokenDescriptor.setUsername(authorization.getName());
-                       tokenDescriptor.setToken(token);
-                       tokenDescriptor.setExpiryDate(expiryDateStr);
-//                     tokenDescriptor.setRoles(Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles))));
-
-                       resp.setContentType("application/json");
-                       JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter());
-                       jg.writeObject(tokenDescriptor);
-               } catch (Exception e) {
-                       new CmsExceptionsChain(e).writeAsJson(objectMapper, resp);
-               }
-       }
-
-       @Override
-       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               // temporarily wrap POST for ease of testing
-               doPost(req, resp);
-       }
-
-       @Override
-       protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               try {
-                       String token = req.getParameter(PARAM_TOKEN);
-                       userManager.expireAuthToken(token);
-               } catch (Exception e) {
-                       new CmsExceptionsChain(e).writeAsJson(objectMapper, resp);
-               }
-       }
-
-       protected <T> T extractFrom(Set<T> creds) {
-               if (creds.size() > 0)
-                       return creds.iterator().next();
-               else
-                       return null;
-       }
-
-       public void setUserManager(CmsUserManager userManager) {
-               this.userManager = userManager;
-       }
-}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java
deleted file mode 100644 (file)
index 1541b4f..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.Serializable;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-/** A serializable descriptor of a token. */
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class TokenDescriptor implements Serializable {
-       private static final long serialVersionUID = -6607393871416803324L;
-
-       private String token;
-       private String username;
-       private String expiryDate;
-//     private Set<String> roles;
-
-       public String getToken() {
-               return token;
-       }
-
-       public void setToken(String token) {
-               this.token = token;
-       }
-
-       public String getUsername() {
-               return username;
-       }
-
-       public void setUsername(String username) {
-               this.username = username;
-       }
-
-//     public Set<String> getRoles() {
-//             return roles;
-//     }
-//
-//     public void setRoles(Set<String> roles) {
-//             this.roles = roles;
-//     }
-
-       public String getExpiryDate() {
-               return expiryDate;
-       }
-
-       public void setExpiryDate(String expiryDate) {
-               this.expiryDate = expiryDate;
-       }
-
-}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java
deleted file mode 100644 (file)
index 1405737..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS integration (JSON, web services). */
-package org.argeo.cms.integration;
\ No newline at end of file
index defc59efcadf1e49926f373fe97781eecdf4c89e..c0dc977547ef3abdda9c51bfe7896013d4de1de1 100644 (file)
@@ -21,7 +21,7 @@ import org.osgi.framework.FrameworkUtil;
 @ServerEndpoint(value = "/cms/status/event/{topic}", configurator = CmsWebSocketConfigurator.class)
 public class EventEndpoint implements CmsEventSubscriber {
        private final static CmsLog log = CmsLog.getLog(EventEndpoint.class);
-       private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext();
+       private BundleContext bc = FrameworkUtil.getBundle(EventEndpoint.class).getBundleContext();
 
        private RemoteEndpoint.Basic remote;
        private CmsEventBus cmsEventBus;
index a8466fee20a84ced621c1de4ad6579370662c0cf..b385bf84d53523ebc7b56a9a05b8f797ca9575a0 100644 (file)
@@ -22,7 +22,6 @@ public class StatusHandler implements WebsocketEndpoints, HttpHandler {
                Set<Class<?>> res = new HashSet<>();
                res.add(PingEndpoint.class);
                res.add(EventEndpoint.class);
-               res.add(TestEndpoint.class);
                return res;
        }
 
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java
deleted file mode 100644 (file)
index f0c7fca..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-package org.argeo.cms.websocket.server;
-
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.websocket.CloseReason;
-import javax.websocket.EndpointConfig;
-import javax.websocket.OnClose;
-import javax.websocket.OnError;
-import javax.websocket.OnMessage;
-import javax.websocket.OnOpen;
-import javax.websocket.RemoteEndpoint;
-import javax.websocket.Session;
-import javax.websocket.server.PathParam;
-import javax.websocket.server.ServerEndpoint;
-
-import org.argeo.api.acr.ldap.NamingUtils;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.integration.CmsExceptionsChain;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventConstants;
-import org.osgi.service.event.EventHandler;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/** Provides WebSocket access. */
-@ServerEndpoint(value = "/cms/status/test/{topic}", configurator = CmsWebSocketConfigurator.class)
-public class TestEndpoint implements EventHandler {
-       private final static CmsLog log = CmsLog.getLog(TestEndpoint.class);
-
-       final static String TOPICS_BASE = "/test";
-       final static String INPUT = "input";
-       final static String TOPIC = "topic";
-       final static String VIEW_UID = "viewUid";
-       final static String COMPUTATION_UID = "computationUid";
-       final static String MESSAGES = "messages";
-       final static String ERRORS = "errors";
-
-       final static String EXCEPTION = "exception";
-       final static String MESSAGE = "message";
-
-       private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext();
-
-       private String wsSessionId;
-       private RemoteEndpoint.Basic remote;
-       private ServiceRegistration<EventHandler> eventHandlerSr;
-
-       // json
-       private ObjectMapper objectMapper = new ObjectMapper();
-
-       private WebSocketView view;
-
-       @OnOpen
-       public void onOpen(Session session, EndpointConfig endpointConfig) {
-               Map<String, List<String>> parameters = NamingUtils.queryToMap(session.getRequestURI());
-               String path = NamingUtils.getQueryValue(parameters, "path");
-               log.debug("WS Path: " + path);
-
-               wsSessionId = session.getId();
-
-               // 24h timeout
-               session.setMaxIdleTimeout(1000 * 60 * 60 * 24);
-
-               Map<String, Object> userProperties = session.getUserProperties();
-               Subject subject = null;
-//             AccessControlContext accessControlContext = (AccessControlContext) userProperties
-//                             .get(ServletContextHelper.REMOTE_USER);
-//             Subject subject = Subject.getSubject(accessControlContext);
-//             // Deal with authentication failure
-//             if (subject == null) {
-//                     try {
-//                             CloseReason.CloseCode closeCode = new CloseReason.CloseCode() {
-//
-//                                     @Override
-//                                     public int getCode() {
-//                                             return 4001;
-//                                     }
-//                             };
-//                             session.close(new CloseReason(closeCode, "Unauthorized"));
-//                             if (log.isTraceEnabled())
-//                                     log.trace("Unauthorized web socket " + wsSessionId + ". Closing with code " + closeCode.getCode()
-//                                                     + ".");
-//                             return;
-//                     } catch (IOException e) {
-//                             // silent
-//                     }
-//                     return;// ignore
-//             }
-
-               if (log.isDebugEnabled())
-                       log.debug("WS#" + wsSessionId + " open for: " + subject);
-               remote = session.getBasicRemote();
-               view = new WebSocketView(subject);
-
-               // OSGi events
-               String[] topics = new String[] { TOPICS_BASE + "/*" };
-               Hashtable<String, Object> ht = new Hashtable<>();
-               ht.put(EventConstants.EVENT_TOPIC, topics);
-               ht.put(EventConstants.EVENT_FILTER, "(" + VIEW_UID + "=" + view.getUid() + ")");
-               eventHandlerSr = bc.registerService(EventHandler.class, this, ht);
-
-               if (log.isDebugEnabled())
-                       log.debug("New view " + view.getUid() + " opened, via web socket.");
-       }
-
-       @OnMessage
-       public void onWebSocketText(@PathParam("topic") String topic, Session session, String message)
-                       throws JsonMappingException, JsonProcessingException {
-               try {
-                       if (log.isTraceEnabled())
-                               log.trace("WS#" + view.getUid() + " received:\n" + message + "\n");
-//                     JsonNode jsonNode = objectMapper.readTree(message);
-//                     String topic = jsonNode.get(TOPIC).textValue();
-
-                       final String computationUid = null;
-//                     if (MY_TOPIC.equals(topic)) {
-//                             view.checkRole(SPECIFIC_ROLE);
-//                             computationUid= process();
-//                     }
-                       remote.sendText("ACK " + topic);
-               } catch (Exception e) {
-                       log.error("Error when receiving web socket message", e);
-                       sendSystemErrorMessage(e);
-               }
-       }
-
-       @OnClose
-       public void onWebSocketClose(CloseReason reason) {
-               if (eventHandlerSr != null)
-                       eventHandlerSr.unregister();
-               if (view != null && log.isDebugEnabled())
-                       log.debug("WS#" + view.getUid() + " closed: " + reason);
-       }
-
-       @OnError
-       public void onWebSocketError(Throwable cause) {
-               if (view != null) {
-                       log.error("WS#" + view.getUid() + " ERROR", cause);
-               } else {
-                       if (log.isTraceEnabled())
-                               log.error("Error in web socket session " + wsSessionId, cause);
-               }
-       }
-
-       @Override
-       public void handleEvent(Event event) {
-               try {
-                       Object uid = event.getProperty(COMPUTATION_UID);
-                       Exception exception = (Exception) event.getProperty(EXCEPTION);
-                       if (exception != null) {
-                               CmsExceptionsChain systemErrors = new CmsExceptionsChain(exception);
-                               String sent = systemErrors.toJsonString(objectMapper);
-                               remote.sendText(sent);
-                               return;
-                       }
-                       String topic = event.getTopic();
-                       if (log.isTraceEnabled())
-                               log.trace("WS#" + view.getUid() + " " + topic + ": notify event " + topic + "#" + uid + ", " + event);
-               } catch (Exception e) {
-                       log.error("Error when handling event for WebSocket", e);
-                       sendSystemErrorMessage(e);
-               }
-
-       }
-
-       /** Sends an error message in JSON format. */
-       protected void sendSystemErrorMessage(Exception e) {
-               CmsExceptionsChain systemErrors = new CmsExceptionsChain(e);
-               try {
-                       if (remote != null)
-                               remote.sendText(systemErrors.toJsonString(objectMapper));
-               } catch (Exception e1) {
-                       log.error("Cannot send WebSocket system error messages " + systemErrors, e1);
-               }
-       }
-}
index d84ce7212ebcbcacd387618c83ca72e7601bb4ec..f08a2a57e0035991072abd457d5c04b53234f304 100644 (file)
@@ -1,6 +1,7 @@
 package org.argeo.cms.jshell;
 
 import java.io.IOException;
+import java.nio.file.ClosedWatchServiceException;
 import java.nio.file.DirectoryStream;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
@@ -39,6 +40,8 @@ public class CmsJShell {
        private Path jtermBase;
        private Path jtermLinkedDir;
 
+       private WatchService watchService;
+
        public void start() throws Exception {
                // TODO better define application id, make it configurable
                String applicationID;
@@ -51,20 +54,39 @@ public class CmsJShell {
                // TODO centralise state run dir
                stateRunDir = OS.getRunDir().resolve(applicationID);
 
+               // TODO factorise create/delete pattern
                jshBase = stateRunDir.resolve(JShellClient.JSH);
+               if (Files.exists(jshBase)) {
+                       log.warn(jshBase + " already exists, deleting it...");
+                       FsUtils.delete(jshBase);
+               }
                Files.createDirectories(jshBase);
-               jshLinkedDir = Files.createSymbolicLink(cmsState.getStatePath(JShellClient.JSH), jshBase);
+               jshLinkedDir = cmsState.getStatePath(JShellClient.JSH);
+               if (Files.exists(jshLinkedDir)) {
+                       log.warn(jshLinkedDir + " already exists, deleting it...");
+                       FsUtils.delete(jshLinkedDir);
+               }
+               Files.createSymbolicLink(jshLinkedDir, jshBase);
 
                jtermBase = stateRunDir.resolve(JShellClient.JTERM);
+               if (Files.exists(jtermBase)) {
+                       log.warn(jtermBase + " already exists, deleting it...");
+                       FsUtils.delete(jtermBase);
+               }
                Files.createDirectories(jtermBase);
-               jtermLinkedDir = Files.createSymbolicLink(cmsState.getStatePath(JShellClient.JTERM), jtermBase);
+               jtermLinkedDir = cmsState.getStatePath(JShellClient.JTERM);
+               if (Files.exists(jtermLinkedDir)) {
+                       log.warn(jtermLinkedDir + " already exists, deleting it...");
+                       FsUtils.delete(jtermLinkedDir);
+               }
+               Files.createSymbolicLink(jtermLinkedDir, jtermBase);
 
                log.info("Local JShell on " + jshBase + ", linked to " + jshLinkedDir);
                log.info("Local JTerm on " + jtermBase + ", linked to " + jtermLinkedDir);
 
                new Thread(() -> {
                        try {
-                               WatchService watchService = FileSystems.getDefault().newWatchService();
+                               watchService = FileSystems.getDefault().newWatchService();
 
                                jshBase.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
                                                StandardWatchEventKinds.ENTRY_DELETE);
@@ -133,8 +155,11 @@ public class CmsJShell {
                                        }
                                        key.reset();
                                }
+                       } catch (ClosedWatchServiceException e) {
+                               if (log.isTraceEnabled())
+                                       log.trace("JShell file watch service was closed");
                        } catch (IOException | InterruptedException e) {
-                               e.printStackTrace();
+                               log.error("Unexpected exception in JShell file watch service", e);
                        }
                }, "JShell local sessions watcher").start();
        }
@@ -156,15 +181,31 @@ public class CmsJShell {
        }
 
        public void stop() {
+               if (watchService != null)
+                       try {
+                               watchService.close();
+                       } catch (IOException e) {
+                               log.error("Cannot close JShell watch service", e);
+                       }
                try {
                        Files.delete(jshLinkedDir);
                } catch (IOException e) {
-                       log.error("Cannot remove " + jshLinkedDir);
+                       log.error("Cannot remove " + jshLinkedDir, e);
+               }
+               try {
+                       FsUtils.delete(jshBase);
+               } catch (IOException e) {
+                       log.error("Cannot remove " + jshBase, e);
                }
                try {
                        Files.delete(jtermLinkedDir);
                } catch (IOException e) {
-                       log.error("Cannot remove " + jtermLinkedDir);
+                       log.error("Cannot remove " + jtermLinkedDir, e);
+               }
+               try {
+                       FsUtils.delete(jtermBase);
+               } catch (IOException e) {
+                       log.error("Cannot remove " + jtermBase, e);
                }
        }
 
index b0b348d9ccfec48a514b93042cd20b2fc2f87757..8e6db220c18c9ea2fec39c6798879109b558b317 100644 (file)
@@ -10,10 +10,10 @@ import javax.servlet.ServletException;
 import javax.websocket.DeploymentException;
 import javax.websocket.server.ServerContainer;
 
-import org.eclipse.jetty.server.session.SessionHandler;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
-import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+import org.eclipse.jetty.ee8.nested.SessionHandler;
+import org.eclipse.jetty.ee8.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee8.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.ee8.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
 
 /** A {@link JettyHttpServer} which is compatible with Equinox servlets. */
 public class CmsJettyServer extends JettyHttpServer {
index 1e64fe075011dd7e39f774281fa0535926d01e91..4b4fe14acb697421005ac2b6af267617fba512e7 100644 (file)
@@ -6,7 +6,7 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.ee8.nested.ContextHandler;
 
 /**
  * A {@link Map} implementation wrapping the attributes of a Jetty
index d6037ba8dbf799b3f5b14a71c8b40ac93226cdb3..de2fa44862db415e81a9c39a92a3588746ea3616 100644 (file)
@@ -1,10 +1,6 @@
 package org.argeo.cms.jetty;
 
-import java.util.AbstractMap;
-import java.util.Enumeration;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 
 import javax.servlet.ServletContext;
 import javax.websocket.DeploymentException;
@@ -12,12 +8,11 @@ import javax.websocket.server.ServerContainer;
 
 import org.argeo.cms.servlet.httpserver.HttpContextServlet;
 import org.argeo.cms.websocket.server.WebsocketEndpoints;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.session.SessionHandler;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
-import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+import org.eclipse.jetty.ee8.nested.SessionHandler;
+import org.eclipse.jetty.ee8.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee8.servlet.ServletHolder;
+import org.eclipse.jetty.ee8.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.ee8.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
 
 import com.sun.net.httpserver.HttpHandler;
 
index 551e54e05044410f334694cf9c5641b512ee2a48..c876f3dbe5682a362fbe7434e9b8291b9374bccc 100644 (file)
@@ -4,14 +4,8 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
-import javax.servlet.ServletContext;
-import javax.websocket.DeploymentException;
-import javax.websocket.server.ServerContainer;
-
 import org.argeo.cms.websocket.server.WebsocketEndpoints;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
-import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+import org.eclipse.jetty.ee8.servlet.ServletContextHandler;
 
 import com.sun.net.httpserver.Authenticator;
 import com.sun.net.httpserver.Filter;
index 98975c3c843ad7c152a4c1815f738a812c6ca130..74f0aaed3d8ddeb3154585386f15a5968d8c54eb 100644 (file)
@@ -16,6 +16,7 @@ import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsState;
 import org.argeo.cms.CmsDeployProperty;
 import org.argeo.cms.http.server.HttpServerUtils;
+import org.eclipse.jetty.ee8.servlet.ServletContextHandler;
 import org.eclipse.jetty.http.UriCompliance;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -24,7 +25,6 @@ import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.SslConnectionFactory;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.ExecutorThreadPool;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
@@ -278,7 +278,8 @@ public class JettyHttpServer extends HttpsServer {
                JettyHttpContext httpContext = contexts.remove(path);
                if (httpContext instanceof ContextHandlerHttpContext contextHandlerHttpContext) {
                        // TODO stop handler first?
-                       contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler());
+                       // FIXME understand compatibility with Jetty 12
+                       //contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler());
                } else {
                        // FIXME apparently servlets cannot be removed in Jetty, we should replace the
                        // handler
index b2a472b449910ef7276fd8ac5434032eea3feb59..d5fec4a83908a62497e0bba08da167642a19dfc6 100644 (file)
@@ -10,8 +10,8 @@ import javax.websocket.server.ServerContainer;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.servlet.httpserver.HttpContextServlet;
 import org.argeo.cms.websocket.server.WebsocketEndpoints;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.ee8.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee8.servlet.ServletHolder;
 
 import com.sun.net.httpserver.HttpHandler;
 
index 8a6def33ac631c2c88eaf8f8daa308c27394d4a4..98bb045441b51a306988f95346127998ba08e50c 100644 (file)
@@ -206,7 +206,10 @@ public class CmsSshServer implements CmsSshd {
                                        (PrivateKey) store.getKey(CmsConstants.NODE, keyStorePassword));
                } catch (IOException | KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException
                                | CertificateException | IllegalArgumentException | UnrecoverableKeyException e) {
-                       log.error("Cannot add node public key to SSH authorized keys", e);
+                       if (log.isTraceEnabled())
+                               log.error("Cannot add node public key to SSH authorized keys", e);
+                       else
+                               log.error("Cannot add node public key to SSH authorized keys: " + e.getMessage());
                        return null;
                }
 
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java b/org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java
deleted file mode 100644 (file)
index 852cb52..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-package org.argeo.cms.media;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import org.apache.batik.transcoder.TranscoderException;
-import org.apache.batik.transcoder.TranscoderInput;
-import org.apache.batik.transcoder.TranscoderOutput;
-import org.apache.batik.transcoder.image.ImageTranscoder;
-import org.apache.batik.transcoder.image.PNGTranscoder;
-
-public class SvgToPng {
-
-       public void convertSvgDir(Path sourceDir, Path targetDir, int width) {
-               System.out.println("##\n## " + width + "px - " + sourceDir + "\n##");
-               try {
-                       if (targetDir == null)
-                               targetDir = sourceDir.getParent().resolve(Integer.toString(width));
-                       Files.createDirectories(targetDir);
-
-                       PNGTranscoder transcoder = new PNGTranscoder();
-                       // transcoder.addTranscodingHint(ImageTranscoder.KEY_BACKGROUND_COLOR,
-                       // Color.WHITE);
-                       transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) width);
-                       transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) width);
-
-                       for (Path source : Files.newDirectoryStream(sourceDir, "*.svg")) {
-                               // FIXME extract base name
-                               String baseName = null; // = FilenameUtils.getBaseName(source.toString());
-                               Path target = targetDir.resolve(baseName + ".png");
-                               convertSvgFile(transcoder, source, target);
-                       }
-               } catch (IOException | TranscoderException e) {
-                       throw new IllegalStateException("Cannot convert from " + sourceDir + " to " + targetDir, e);
-               }
-
-       }
-
-       protected void convertSvgFile(ImageTranscoder transcoder, Path source, Path target)
-                       throws IOException, TranscoderException {
-               try (Reader reader = Files.newBufferedReader(source); OutputStream out = Files.newOutputStream(target);) {
-                       TranscoderInput input = new TranscoderInput(reader);
-//                     BufferedImage image = transcoder.createImage(32, 32);
-                       TranscoderOutput output = new TranscoderOutput(out);
-                       transcoder.transcode(input, output);
-                       System.out.println(source.getFileName() + " -> " + target);
-               }
-       }
-
-       public static void main(String[] args) throws Exception {
-
-               Path path = Paths.get(args[0]);
-
-               SvgToPng svgToPng = new SvgToPng();
-               svgToPng.convertSvgDir(path, null, 16);
-               svgToPng.convertSvgDir(path, null, 32);
-               svgToPng.convertSvgDir(path, null, 64);
-               svgToPng.convertSvgDir(path, null, 96);
-       }
-}
index 84e471aafbe2103403a24ed7535c941469f0ff36..f9ced18df866c5780cf28b36ef4191d3e2269f44 100644 (file)
@@ -7,6 +7,7 @@ import org.argeo.api.cms.ux.Cms2DSize;
 import org.argeo.api.cms.ux.CmsView;
 import org.argeo.cms.util.CurrentSubject;
 
+/** Utilities around UX. */
 public class CmsUxUtils {
        public static ContentSession getContentSession(ContentRepository contentRepository, CmsView cmsView) {
                return CurrentSubject.callAs(cmsView.getCmsSession().getSubject(), () -> contentRepository.get());
@@ -16,11 +17,6 @@ public class CmsUxUtils {
                return content.getName().getLocalPart();
        }
 
-       /** singleton */
-       private CmsUxUtils() {
-
-       }
-
        public static StringBuilder imgBuilder(String src, String width, String height) {
                return new StringBuilder(64).append("<img width='").append(width).append("' height='").append(height)
                                .append("' src='").append(src).append("'");
@@ -33,4 +29,10 @@ public class CmsUxUtils {
        public static String img(String src, Cms2DSize size) {
                return img(src, Integer.toString(size.width()), Integer.toString(size.height()));
        }
+
+       /** singleton */
+       private CmsUxUtils() {
+
+       }
+
 }
index 7270c0896255f4596f73cc489a7819f95f5bf791..357a0102f432f639058ce4deff06c546e5be3625 100644 (file)
@@ -11,6 +11,7 @@ import java.util.Enumeration;
 
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.uuid.ConcurrentUuidFactory;
+import org.argeo.api.uuid.NodeIdSupplier;
 import org.argeo.api.uuid.UuidBinaryUtils;
 
 public class CmsUuidFactory extends ConcurrentUuidFactory {
@@ -73,8 +74,10 @@ public class CmsUuidFactory extends ConcurrentUuidFactory {
                        }
                }
                InetAddress selectedIp = selectedIpv6 != null ? selectedIpv6 : selectedIpv4;
-               if (selectedIp == null)
-                       throw new IllegalStateException("No IP address found");
+               if (selectedIp == null) {
+                       log.warn("No IP address found, using a random node id for UUID generation");
+                       return NodeIdSupplier.randomNodeId();
+               }
                byte[] digest = sha1(selectedIp.getAddress());
                log.info("Use IP " + selectedIp + " hashed as " + UuidBinaryUtils.toHexString(digest) + " as node id");
                byte[] nodeId = toNodeIdBytes(digest, 0);
index 47b36f446f003744d61d4c56e3ccc31ba6a122f6..638547d2471a3d94e69d7de82a7595d60ea29e0e 100644 (file)
@@ -283,13 +283,15 @@ public class UserAdminLoginModule implements LoginModule {
                if (log.isDebugEnabled()) {
                        StringBuilder msg = new StringBuilder();
                        msg.append("Logged in to CMS: '" + authorization + "' (" + authorization.getName() + ")\n");
-                       for (Principal principal : subject.getPrincipals()) {
-                               msg.append("  Principal: " + principal.getName()).append(" (")
-                                               .append(principal.getClass().getSimpleName()).append(")\n");
-                       }
-                       for (Object credential : subject.getPublicCredentials()) {
-                               msg.append("  Public Credential: " + credential).append(" (")
-                                               .append(credential.getClass().getSimpleName()).append(")\n");
+                       if (log.isTraceEnabled()) {
+                               for (Principal principal : subject.getPrincipals()) {
+                                       msg.append("  Principal: " + principal.getName()).append(" (")
+                                                       .append(principal.getClass().getSimpleName()).append(")\n");
+                               }
+                               for (Object credential : subject.getPublicCredentials()) {
+                                       msg.append("  Public Credential: " + credential).append(" (")
+                                                       .append(credential.getClass().getSimpleName()).append(")\n");
+                               }
                        }
                        log.debug(msg);
                }
index bd54b20594b5e7200d0a3e04f975e8cfc13ab354..01d285b8cfd2767b8d4252b5cb93cb30861950a9 100644 (file)
@@ -44,6 +44,9 @@ public class CmsContextImpl implements CmsContext {
 
        private Long availableSince;
 
+       // time in ms to wait for CMS to be ready
+       private final long readynessTimeout = 30 * 1000;
+
        // CMS sessions
        private Map<UUID, CmsSessionImpl> cmsSessionsByUuid = new HashMap<>();
        private Map<String, CmsSessionImpl> cmsSessionsByLocalId = new HashMap<>();
@@ -56,9 +59,16 @@ public class CmsContextImpl implements CmsContext {
                defaultLocale = locales.get(0);
 
                new Thread(() -> {
-                       while (!checkReadiness()) {
+                       long begin = System.currentTimeMillis();
+                       long duration = 0;
+                       readyness: while (!checkReadiness()) {
+                               duration = System.currentTimeMillis() - begin;
+                               if (duration > readynessTimeout) {
+                                       log.error("## CMS not ready after " + duration + " ms. Giving up checking.");
+                                       break readyness;
+                               }
                                try {
-                                       Thread.sleep(500);
+                                       Thread.sleep(100);
                                } catch (InterruptedException e) {
                                }
                        }
index f9a1dc36832dab593fb28d53017679f8db5f400c..e1c420b8287469bb698af791d04b9d87638563d9 100644 (file)
@@ -82,7 +82,7 @@ public class CmsDeploymentImpl implements CmsDeployment {
                CmsAuthenticator authenticator = isPublic ? new PublicCmsAuthenticator() : new CmsAuthenticator();
                httpHandlers.put(contextPath, httpHandler);
                httpAuthenticators.put(contextPath, authenticator);
-               if (httpServer == null) {
+               if (httpServer.join() == null) {
                        return;
                } else {
                        createHttpContext(contextPath, httpHandler, authenticator);
@@ -107,16 +107,16 @@ public class CmsDeploymentImpl implements CmsDeployment {
                if (contextPath == null)
                        return; // ignore silently
                httpHandlers.remove(contextPath);
-               if (httpServer == null)
+               if (httpServer.join() == null)
                        return;
                httpServer.join().removeContext(contextPath);
                log.debug(() -> "Removed handler " + contextPath + " : " + httpHandler.getClass().getName());
        }
 
        public boolean allExpectedServicesAvailable() {
-               if (httpExpected && httpServer == null)
+               if (httpExpected && !httpServer.isDone())
                        return false;
-               if (sshdExpected && cmsSshd == null)
+               if (sshdExpected && !cmsSshd.isDone())
                        return false;
                return true;
        }
index 943c06f4ec44a6914e73a41e0530c7e61aed5381..b35b4be09311b8392531f1fe2e6ae306bc88adf9 100644 (file)
@@ -138,6 +138,7 @@ class KernelUtils implements KernelConstants {
                if (uri == null)
                        throw new IllegalArgumentException("URI cannot be null");
                try {
+                       // FIXME does not work if URI contains illegal characters (such as spaces, etc.)
                        return new URI(uri);
                } catch (URISyntaxException e) {
                        throw new IllegalArgumentException("Badly formatted URI " + uri, e);
index 5fbef6b31a6889f9ba3276150032988214d0e293..f4242e6c273bbea6de0dd392256d40915109b9f6 100644 (file)
@@ -16,13 +16,9 @@ import java.util.StringJoiner;
 public class StreamUtils {
        private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
 
-       /*
-        * APACHE COMMONS IO (inspired)
-        */
-
        /** @return the number of bytes */
-       public static Long copy(InputStream in, OutputStream out) throws IOException {
-               Long count = 0l;
+       public static long copy(InputStream in, OutputStream out) throws IOException {
+               long count = 0l;
                byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
                while (true) {
                        int length = in.read(buf);
@@ -35,8 +31,8 @@ public class StreamUtils {
        }
 
        /** @return the number of chars */
-       public static Long copy(Reader in, Writer out) throws IOException {
-               Long count = 0l;
+       public static long copy(Reader in, Writer out) throws IOException {
+               long count = 0l;
                char[] buf = new char[DEFAULT_BUFFER_SIZE];
                while (true) {
                        int length = in.read(buf);
index c63fdcd376daffa6ac0492f78ae38bef28ff5fd9..b080a7513b97a3b10c07870eb3ec328af056f2d6 100644 (file)
@@ -3,10 +3,14 @@ package org.argeo.init;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Properties;
@@ -26,6 +30,8 @@ public class Service {
 
        private static RuntimeContext runtimeContext = null;
 
+       private static List<Runnable> postStart = Collections.synchronizedList(new ArrayList<>());
+
        protected Service(String[] args) {
        }
 
@@ -118,11 +124,25 @@ public class Service {
                                OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext(config);
                                osgiRuntimeContext.run();
                                Service.runtimeContext = osgiRuntimeContext;
+                               for (Runnable run : postStart) {
+                                       try {
+                                               run.run();
+                                       } catch (Exception e) {
+                                               logger.log(Level.ERROR, "Cannot run post start callback " + run, e);
+                                       }
+                               }
                                Service.runtimeContext.waitForStop(0);
-                       } catch (NoClassDefFoundError e) {
+                       } catch (NoClassDefFoundError noClassDefFoundE) {
                                StaticRuntimeContext staticRuntimeContext = new StaticRuntimeContext((Map<String, String>) config);
                                staticRuntimeContext.run();
                                Service.runtimeContext = staticRuntimeContext;
+                               for (Runnable run : postStart) {
+                                       try {
+                                               run.run();
+                                       } catch (Exception e) {
+                                               logger.log(Level.ERROR, "Cannot run post start callback " + run, e);
+                                       }
+                               }
                                Service.runtimeContext.waitForStop(0);
                        }
                } catch (Exception e) {
@@ -136,4 +156,9 @@ public class Service {
        public static RuntimeContext getRuntimeContext() {
                return runtimeContext;
        }
+
+       /** Add a post-start call back to be run after the runtime has been started. */
+       public static void addPostStart(Runnable runnable) {
+               postStart.add(runnable);
+       }
 }
index cc2f564716858b5dee3389404bf7fcf8a431dd1e..8942706300ee74b423c6e5cec959e68e600b9faa 100644 (file)
@@ -28,10 +28,11 @@ public class A2Component implements Comparable<A2Component> {
        }
 
        A2Branch getOrAddBranch(String branchId) {
-               if (branches.containsKey(branchId))
-                       return branches.get(branchId);
-               else
-                       return new A2Branch(this, branchId);
+               if (!branches.containsKey(branchId)) {
+                       A2Branch a2Branch = new A2Branch(this, branchId);
+                       branches.put(branchId, a2Branch);
+               }
+               return branches.get(branchId);
        }
 
        A2Module getOrAddModule(Version version, Object locator) {
index 151b0023f56a2fc9d052dee6c31f1924e0609f52..921992da3371f0e12af8175b1d2f8911c5d46075 100644 (file)
@@ -61,9 +61,9 @@ public class FsA2Source extends AbstractProvisioningSource implements A2Source {
                                        if (variantPath == null)
                                                continue contributions;
 
-                                       if (Files.exists(variantPath)) {
-                                               // a variant was found, let's collect its contributions (also common ones in its
-                                               // parent)
+                                       // a variant was found, let's collect its contributions (also common ones in its
+                                       // parent)
+                                       if (Files.exists(variantPath.getParent())) {
                                                for (Path variantContributionPath : Files.newDirectoryStream(variantPath.getParent())) {
                                                        String variantContributionId = variantContributionPath.getFileName().toString();
                                                        if (variantContributionId.contains(".")) {
@@ -71,6 +71,8 @@ public class FsA2Source extends AbstractProvisioningSource implements A2Source {
                                                                contributions.put(variantContributionPath, contribution);
                                                        }
                                                }
+                                       }
+                                       if (Files.exists(variantPath)) {
                                                for (Path variantContributionPath : Files.newDirectoryStream(variantPath)) {
                                                        String variantContributionId = variantContributionPath.getFileName().toString();
                                                        if (variantContributionId.contains(".")) {
index 0064ab9eddbb1c4d3c8c37148eb9efaaa68f61aa..7f1133f676a034a8b1fde29a4a683877dbe46b1b 100644 (file)
@@ -6,13 +6,18 @@ import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.Version;
 
-/** A running OSGi bundle context seen as a {@link AbstractProvisioningSource}. */
+/**
+ * A running OSGi bundle context seen as a {@link AbstractProvisioningSource}.
+ */
 class OsgiContext extends AbstractProvisioningSource {
        private final BundleContext bc;
 
+       private A2Contribution runtimeContribution;
+
        public OsgiContext(BundleContext bc) {
                super(false);
                this.bc = bc;
+               runtimeContribution = getOrAddContribution(A2Contribution.RUNTIME);
        }
 
        public OsgiContext() {
@@ -25,16 +30,19 @@ class OsgiContext extends AbstractProvisioningSource {
        }
 
        void load() {
-               A2Contribution runtimeContribution = getOrAddContribution( A2Contribution.RUNTIME);
                for (Bundle bundle : bc.getBundles()) {
-                       // OsgiBootUtils.debug(bundle.getDataFile("/"));
-                       String componentId = bundle.getSymbolicName();
-                       Version version = bundle.getVersion();
-                       A2Component component = runtimeContribution.getOrAddComponent(componentId);
-                       A2Module module = component.getOrAddModule(version, bundle);
-                       if (OsgiBootUtils.isDebug())
-                               OsgiBootUtils.debug("Registered " + module + " (location id: " + bundle.getLocation() + ")");
+                       registerBundle(bundle);
                }
 
        }
+
+       void registerBundle(Bundle bundle) {
+               String componentId = bundle.getSymbolicName();
+               Version version = bundle.getVersion();
+               A2Component component = runtimeContribution.getOrAddComponent(componentId);
+               A2Module module = component.getOrAddModule(version, bundle);
+               if (OsgiBootUtils.isDebug())
+                       OsgiBootUtils.debug("Registered bundle module " + module + " (location id: " + bundle.getLocation() + ")");
+
+       }
 }
index 6a0836bdfdea80ffc6727140e49d2dd3dcd3a470..289870abc3ef945b7a2bb5f9b122027beb1af6cd 100644 (file)
@@ -149,21 +149,30 @@ public class ProvisioningManager {
                        Version moduleVersion = module.getVersion();
                        A2Branch osgiBranch = osgiContext.findBranch(module.getBranch().getComponent().getId(), moduleVersion);
                        if (osgiBranch == null) {
-//                             Bundle bundle = bc.installBundle(module.getBranch().getCoordinates(),
-//                                             moduleSource.newInputStream(module.getLocator()));
                                Bundle bundle = moduleSource.install(bc, module);
-                               if (OsgiBootUtils.isDebug())
-                                       OsgiBootUtils.debug("Installed bundle " + bundle.getLocation() + " with version " + moduleVersion);
+                               // TODO make it more dynamic, based on OSGi APIs
+                               osgiContext.registerBundle(bundle);
+//                             if (OsgiBootUtils.isDebug())
+//                                     OsgiBootUtils.debug("Installed bundle " + bundle.getLocation() + " with version " + moduleVersion);
                                return bundle;
                        } else {
                                A2Module lastOsgiModule = osgiBranch.last();
                                int compare = moduleVersion.compareTo(lastOsgiModule.getVersion());
-                               if (compare > 0) {// update
+                               if (compare >= 0) {// update (also if same version)
                                        Bundle bundle = (Bundle) lastOsgiModule.getLocator();
-//                                     bundle.update(moduleSource.newInputStream(module.getLocator()));
+                                       if (bundle.getBundleId() == 0)// ignore framework bundle
+                                               return null;
                                        moduleSource.update(bundle, module);
+                                       // TODO make it more dynamic, based on OSGi APIs
+                                       // TODO remove old module? Or keep update history?
+                                       osgiContext.registerBundle(bundle);
                                        OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
                                        return bundle;
+                               } else {
+                                       if (OsgiBootUtils.isDebug())
+                                               OsgiBootUtils.debug("Did not install as bundle module " + module
+                                                               + " since a module with higher version " + lastOsgiModule.getVersion()
+                                                               + " is already installed for branch " + osgiBranch);
                                }
                        }
                } catch (Exception e) {
index c2ce215288171bf94543a5fd0a949013b25b6bf2..ff602ad51a7c06abe532fc5af3111f9c18870a93 100644 (file)
@@ -1,9 +1,14 @@
 package org.argeo.init.logging;
 
+import java.io.IOException;
 import java.io.PrintStream;
 import java.io.Serializable;
 import java.lang.System.Logger;
 import java.lang.System.Logger.Level;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.text.MessageFormat;
 import java.time.Instant;
 import java.util.Collections;
@@ -43,10 +48,12 @@ class ThinLogging implements Consumer<Map<String, Object>> {
         */
        final static String PROP_ARGEO_LOGGING_SYNCHRONOUS = "argeo.logging.synchronous";
        /**
-        * Whether to enable jounrald compatible output, either: auto (default), true,
+        * Whether to enable journald compatible output, either: auto (default), true,
         * or false.
         */
        final static String PROP_ARGEO_LOGGING_JOURNALD = "argeo.logging.journald";
+       /** A file to which additionally write log entries. */
+       final static String PROP_ARGEO_LOGGING_FILE = "argeo.logging.file";
        /**
         * The level from which call location (that is, line number in Java code) will
         * be searched (default is WARNING)
@@ -67,6 +74,7 @@ class ThinLogging implements Consumer<Map<String, Object>> {
 
        private final LogEntryPublisher publisher;
        private PrintStreamSubscriber synchronousSubscriber;
+       private PrintStream fileOut;
 
        private final boolean journald;
        private final Level callLocationLevel;
@@ -82,6 +90,21 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                        PrintStreamSubscriber subscriber = new PrintStreamSubscriber();
                        publisher = new LogEntryPublisher();
                        publisher.subscribe(subscriber);
+                       String logFileStr = System.getProperty(PROP_ARGEO_LOGGING_FILE);
+                       if (logFileStr != null) {
+                               Path logFilePath = Paths.get(logFileStr);
+                               if (!Files.exists(logFilePath.getParent())) {
+                                       System.err.println("Parent directory of " + logFilePath + " does not exist");
+                               } else {
+                                       try {
+                                               fileOut = new PrintStream(Files.newOutputStream(logFilePath), true, StandardCharsets.UTF_8);
+                                               publisher.subscribe(new PrintStreamSubscriber(fileOut, fileOut));
+                                       } catch (IOException e) {
+                                               System.err.println("Cannot write log to " + logFilePath);
+                                               e.printStackTrace();
+                                       }
+                               }
+                       }
                }
                Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown"));
 
@@ -147,6 +170,14 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                                // silent
                        }
                }
+
+               if (fileOut != null) {
+                       try {
+                               fileOut.close();
+                       } catch (Exception e) {
+                               // silent
+                       }
+               }
        }
 
        private Level computeApplicableLevel(String name) {
index 2e96db3b37a769774f0c7b7d980ec1cb494549dd..f5b260caba404735f9f296830bab0e6e29c369bf 100644 (file)
@@ -381,7 +381,23 @@ public class OsgiBoot implements OsgiBootConstants {
                        for (String bsn : startLevels.get(level))
                                bundleStartLevels.put(bsn, level);
                }
-               for (Bundle bundle : bundleContext.getBundles()) {
+
+               // keep only bundles with the highest version
+               Map<String, Bundle> startableBundles = new HashMap<>();
+               bundles: for (Bundle bundle : bundleContext.getBundles()) {
+                       if (bundle.getVersion() == null)
+                               continue bundles;
+                       String bsn = bundle.getSymbolicName();
+                       if (!startableBundles.containsKey(bsn)) {
+                               startableBundles.put(bsn, bundle);
+                       } else {
+                               if (bundle.getVersion().compareTo(startableBundles.get(bsn).getVersion()) > 0) {
+                                       startableBundles.put(bsn, bundle);
+                               }
+                       }
+               }
+
+               for (Bundle bundle : startableBundles.values()) {
                        String bsn = bundle.getSymbolicName();
                        if (bundleStartLevels.containsKey(bsn)) {
                                BundleStartLevel bundleStartLevel = bundle.adapt(BundleStartLevel.class);
@@ -394,7 +410,7 @@ public class OsgiBoot implements OsgiBootConstants {
                                                OsgiBootUtils.error("Cannot mark " + bsn + " as started", e);
                                        }
                                        if (OsgiBootUtils.isDebug())
-                                               OsgiBootUtils.debug(bsn + " starts at level " + level);
+                                               OsgiBootUtils.debug(bsn + " v" + bundle.getVersion() + " starts at level " + level);
                                }
                        }
                }
index 2ced2a26c0d496cc2ba14301025a6dcf39bbee5f..c0d2b8caf7cc3e54b9cf5ab223c2e1d36bf2d6ce 100644 (file)
@@ -1,2 +1,6 @@
 Service-Component: \
 OSGI-INF/equinoxJettyServer.xml,\
+
+Import-Package:\
+org.eclipse.jetty.session,\
+*
index e6595a05e9d8379f9f2a25d46a85fd83dbcb3272..cd4d5cee80949b754c4b6b71b14eda82bd8df78a 100644 (file)
@@ -16,9 +16,9 @@ import javax.servlet.http.HttpSessionListener;
 
 import org.argeo.cms.jetty.CmsJettyServer;
 import org.eclipse.equinox.http.servlet.HttpServiceServlet;
-import org.eclipse.jetty.server.session.SessionHandler;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.ee8.nested.SessionHandler;
+import org.eclipse.jetty.ee8.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee8.servlet.ServletHolder;
 import org.osgi.framework.Constants;
 
 /** A {@link CmsJettyServer} integrating with Equinox HTTP framework. */
index 2cd600152d86674a6bcb84ed2fbd88d8e2bb140b..71236ee1d529ae193a8d026e4551018beae64f9f 100644 (file)
@@ -6,8 +6,6 @@ import java.util.Hashtable;
 import java.util.Map;
 import java.util.concurrent.ForkJoinPool;
 
-import javax.websocket.DeploymentException;
-import javax.websocket.server.ServerContainer;
 import javax.websocket.server.ServerEndpointConfig;
 
 import org.argeo.api.cms.CmsConstants;
@@ -16,13 +14,10 @@ import org.argeo.api.cms.CmsState;
 import org.argeo.cms.CmsDeployProperty;
 import org.argeo.cms.util.LangUtils;
 import org.argeo.cms.websocket.server.CmsWebSocketConfigurator;
-import org.argeo.cms.websocket.server.TestEndpoint;
 import org.eclipse.equinox.http.jetty.JettyConfigurator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
 
 public class JettyConfig {
        private final static CmsLog log = CmsLog.getLog(JettyConfig.class);
@@ -44,29 +39,29 @@ public class JettyConfig {
                        startServer(properties);
                });
 
-               ServiceTracker<ServerContainer, ServerContainer> serverSt = new ServiceTracker<ServerContainer, ServerContainer>(
-                               bc, ServerContainer.class, null) {
-
-                       @Override
-                       public ServerContainer addingService(ServiceReference<ServerContainer> reference) {
-                               ServerContainer serverContainer = super.addingService(reference);
-
-                               BundleContext bc = reference.getBundle().getBundleContext();
-                               ServiceReference<ServerEndpointConfig.Configurator> srConfigurator = bc
-                                               .getServiceReference(ServerEndpointConfig.Configurator.class);
-                               ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator);
-                               ServerEndpointConfig config = ServerEndpointConfig.Builder
-                                               .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build();
-                               try {
-                                       serverContainer.addEndpoint(config);
-                               } catch (DeploymentException e) {
-                                       throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e);
-                               }
-                               return serverContainer;
-                       }
-
-               };
-               serverSt.open();
+//             ServiceTracker<ServerContainer, ServerContainer> serverSt = new ServiceTracker<ServerContainer, ServerContainer>(
+//                             bc, ServerContainer.class, null) {
+//
+//                     @Override
+//                     public ServerContainer addingService(ServiceReference<ServerContainer> reference) {
+//                             ServerContainer serverContainer = super.addingService(reference);
+//
+//                             BundleContext bc = reference.getBundle().getBundleContext();
+//                             ServiceReference<ServerEndpointConfig.Configurator> srConfigurator = bc
+//                                             .getServiceReference(ServerEndpointConfig.Configurator.class);
+//                             ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator);
+//                             ServerEndpointConfig config = ServerEndpointConfig.Builder
+//                                             .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build();
+//                             try {
+//                                     serverContainer.addEndpoint(config);
+//                             } catch (DeploymentException e) {
+//                                     throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e);
+//                             }
+//                             return serverContainer;
+//                     }
+//
+//             };
+//             serverSt.open();
 
                // check initialisation
 //             ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
index 7be23fc0f595017f3147d8fd4fc6bd06eef1b140..e10dcf0d1c621636f15fd6ef2f287ab947215fa3 100644 (file)
@@ -10,10 +10,10 @@ import org.eclipse.equinox.http.jetty.JettyCustomizer;
 import org.eclipse.jetty.server.ConnectionFactory;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.server.SslConnectionFactory;
-import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.ee8.servlet.ServletContextHandler;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
-import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+import org.eclipse.jetty.ee8.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.ee8.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 
index f2ebcc21eecda1ee5db65700cf16f4833a7190b0..cabcc3462226b71849ca42301c21e05b63f150c2 160000 (submodule)
@@ -1 +1 @@
-Subproject commit f2ebcc21eecda1ee5db65700cf16f4833a7190b0
+Subproject commit cabcc3462226b71849ca42301c21e05b63f150c2
index 16c51ab9777dc35cff0632073a3cfd3579d14fc9..b8300134b8711ae072d401fa915e8e960195dc8d 100644 (file)
@@ -1,6 +1,6 @@
 major=2
 minor=3
-micro=23
+micro=27
 qualifier=
 
 Bundle-Copyright= \
index 708ab5340dda65fe79f15f7a75529926d4e02e5d..4fe2113be1af0a536f9473ffd6e3d9ba5c87cab9 100644 (file)
@@ -21,7 +21,7 @@ argeo.node.repo.type=h2
 argeo.http.port=7070
 #argeo.http.host=[IP address to listen to]
 #argeo.https.port=7073
-#argeo.sshd.port=2222
+argeo.sshd.port=2222
 
 # Logging
 log.org.argeo=DEBUG
@@ -62,6 +62,7 @@ log.org.argeo=DEBUG
 # DON'T CHANGE BELOW
 org.eclipse.equinox.http.jetty.autostart=false
 org.osgi.framework.system.packages.extra=\
+sun.security.util,\
 sun.security.internal.spec,\
 sun.security.provider,\
 com.sun.net.httpserver,\
index 06bb9be3727c1a81f9ad2dd8f598da15611a38d6..127be0856195fecf7b0d484a16e654030f6849c4 100644 (file)
@@ -2,6 +2,8 @@ package org.argeo.cms.swt;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
@@ -24,6 +26,20 @@ import org.eclipse.swt.widgets.Display;
 public abstract class AbstractSwtCmsView implements CmsView {
        private final static CmsLog log = CmsLog.getLog(AbstractSwtCmsView.class);
 
+       /** A timer to be used to perform background UX tasks. */
+       private final static Timer uxTimer = new Timer(true);
+
+       static {
+               // purge every day
+               uxTimer.schedule(new TimerTask() {
+
+                       @Override
+                       public void run() {
+                               uxTimer.purge();
+                       }
+               }, 0, 24 * 60 * 60 * 1000);
+       }
+
        protected final String uiName;
 
        protected LoginContext loginContext;
@@ -138,4 +154,37 @@ public abstract class AbstractSwtCmsView implements CmsView {
                }
        }
 
+       @Override
+       public TimerTask schedule(Runnable task, long delay) {
+               TimerTask timerTask = newSwtUxTimerTask(task);
+               uxTimer.schedule(timerTask, delay);
+               return timerTask;
+       }
+
+       @Override
+       public TimerTask schedule(Runnable task, long delay, long period) {
+               TimerTask timerTask = newSwtUxTimerTask(task);
+               uxTimer.schedule(timerTask, delay, period);
+               return timerTask;
+       }
+
+       protected TimerTask newSwtUxTimerTask(Runnable todo) {
+               return new TimerTask() {
+
+                       @Override
+                       public void run() {
+                               synchronized (display) {
+                                       try {
+                                               if (!display.isDisposed()) {
+                                                       display.syncExec(() -> {
+                                                               todo.run();
+                                                       });
+                                               }
+                                       } catch (Exception e) {
+                                               log.error("Cannot run UX timer task", e);
+                                       }
+                               }
+                       }
+               };
+       }
 }
index e0f63e45e2eb85578428923964f1421fa1ba25fe..d02ad3aec371202cb8f4c42364b15a45277b5605 100644 (file)
@@ -10,17 +10,77 @@ public class CmsSwtUi extends Composite implements CmsUi {
 
        private static final long serialVersionUID = -107939076610406448L;
 
+       /** Last time the UI was accessed. */
+       private long lastAccess = System.currentTimeMillis();
+//     private TimerTask timeoutTask;
+       private long uiTimeout = 0;
+
        private CmsView cmsView;
 
        public CmsSwtUi(Composite parent, int style) {
                super(parent, style);
                cmsView = CmsSwtUtils.getCmsView(parent);
-
                setLayout(new GridLayout());
        }
 
+       @Override
        public CmsView getCmsView() {
                return cmsView;
        }
 
+       @Override
+       public void updateLastAccess() {
+               this.lastAccess = System.currentTimeMillis();
+       }
+
+       public void setUiTimeout(long uiTimeout) {
+//             clearTimeoutTask();
+               this.uiTimeout = uiTimeout;
+               if (this.uiTimeout <= 0)
+                       return;
+               // TODO introduce mechanism to check whether the UI is "zombie"
+               // (that is the UI thread still exists, but cannot execute anything)
+//             final long timeoutTaskPeriod = 60 * 60 * 1000;// 1h
+//             timeoutTask = cmsView.schedule(() -> {
+//                     disposeIfTimedout();
+//             }, timeoutTaskPeriod, timeoutTaskPeriod);
+//             addDisposeListener((e) -> {
+//                     clearTimeoutTask();
+//             });
+       }
+
+//     /** Must be run in UI thread. */
+//     public void disposeIfTimedout() {
+//             System.out.println("Enter disposeIfTimedout");
+//             if (isDisposed()) {
+//                     clearTimeoutTask();
+//                     return;
+//             }
+//             if (isTimedOut()) {
+//                     dispose();
+//                     clearTimeoutTask();
+//                     System.out.println("Disposed after timeout");
+//             }
+//     }
+
+//     private void clearTimeoutTask() {
+//             if (timeoutTask != null) {
+//                     timeoutTask.cancel();
+//                     timeoutTask = null;
+//             }
+//     }
+
+       @Override
+       public boolean isTimedOut() {
+               return uiTimeout > 0 && (System.currentTimeMillis() - lastAccess >= uiTimeout);
+       }
+
+//     class DisposeIfTimedOutTask implements Runnable {
+//             public void run() {
+//                     disposeIfTimedout();
+//                     getDisplay().timerExec(1000, new DisposeIfTimedOutTask());
+//             }
+//
+//     }
+
 }
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java
deleted file mode 100644 (file)
index 74de83e..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.argeo.cms.swt.osgi;
-
-import java.awt.Color;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.System.Logger;
-import java.lang.System.Logger.Level;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-
-import org.apache.batik.transcoder.TranscoderException;
-import org.apache.batik.transcoder.TranscoderInput;
-import org.apache.batik.transcoder.TranscoderOutput;
-import org.apache.batik.transcoder.image.ImageTranscoder;
-import org.apache.batik.transcoder.image.PNGTranscoder;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.framework.BundleContext;
-
-/** Theme which can dynamically create icons from SVG data. */
-public class BundleSvgTheme extends BundleCmsSwtTheme {
-       private final static Logger logger = System.getLogger(BundleSvgTheme.class.getName());
-
-       private Map<String, Map<Integer, ImageData>> imageDataCache = Collections.synchronizedMap(new HashMap<>());
-
-       private Map<Integer, ImageTranscoder> transcoders = Collections.synchronizedMap(new HashMap<>());
-
-       private final static String IMAGE_CACHE_KEY = BundleSvgTheme.class.getName() + ".imageCache";
-
-       @Override
-       public Image getIcon(String name, Integer preferredSize) {
-               String path = "icons/types/svg/" + name + ".svg";
-               return createImageFromSvg(path, preferredSize);
-       }
-
-       @SuppressWarnings("unchecked")
-       protected Image createImageFromSvg(String path, Integer preferredSize) {
-               Display display = Display.getCurrent();
-               Objects.requireNonNull(display, "Not a user interface thread");
-
-               Map<String, Map<Integer, Image>> imageCache = (Map<String, Map<Integer, Image>>) display
-                               .getData(IMAGE_CACHE_KEY);
-               if (imageCache == null)
-                       display.setData(IMAGE_CACHE_KEY, new HashMap<String, Map<Integer, Image>>());
-               imageCache = (Map<String, Map<Integer, Image>>) display.getData(IMAGE_CACHE_KEY);
-
-               Image image = null;
-               if (imageCache.containsKey(path)) {
-                       image = imageCache.get(path).get(preferredSize);
-               }
-               if (image != null)
-                       return image;
-               ImageData imageData = loadFromSvg(path, preferredSize);
-               image = new Image(display, imageData);
-               if (!imageCache.containsKey(path))
-                       imageCache.put(path, Collections.synchronizedMap(new HashMap<>()));
-               imageCache.get(path).put(preferredSize, image);
-               return image;
-       }
-
-       protected ImageData loadFromSvg(String path, int size) {
-               ImageData imageData = null;
-               if (imageDataCache.containsKey(path))
-                       imageData = imageDataCache.get(path).get(size);
-               if (imageData != null)
-                       return imageData;
-
-               ImageTranscoder transcoder = null;
-               synchronized (this) {
-                       transcoder = transcoders.get(size);
-                       if (transcoder == null) {
-                               transcoder = new PNGTranscoder();
-                               transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) size);
-                               transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) size);
-                               transcoder.addTranscodingHint(PNGTranscoder.KEY_BACKGROUND_COLOR, new Color(255, 255, 255, 0));
-                               transcoders.put(size, transcoder);
-                       }
-               }
-               try (InputStream in = getResourceAsStream(path); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
-                       if (in == null)
-                               throw new IllegalArgumentException(path + " not found");
-                       TranscoderInput input = new TranscoderInput(in);
-                       TranscoderOutput output = new TranscoderOutput(out);
-                       transcoder.transcode(input, output);
-                       try (InputStream imageIn = new ByteArrayInputStream(out.toByteArray())) {
-                               imageData = new ImageData(imageIn);
-                       }
-                       logger.log(Level.DEBUG, () -> "Generated " + size + "x" + size + " PNG icon from " + path);
-               } catch (IOException | TranscoderException e) {
-                       throw new RuntimeException("Cannot transcode SVG " + path, e);
-               }
-
-               // cache it
-               if (!imageDataCache.containsKey(path))
-                       imageDataCache.put(path, Collections.synchronizedMap(new HashMap<>()));
-               imageDataCache.get(path).put(size, imageData);
-
-               return imageData;
-       }
-
-       @Override
-       public void init(BundleContext bundleContext, Map<String, String> properties) {
-               super.init(bundleContext, properties);
-
-               // preload all icons
-//             paths: for (String p : getImagesPaths()) {
-//                     if (!p.endsWith(".svg"))
-//                             continue paths;
-//                     createImageFromSvg(p, getDefaultIconSize());
-//             }
-       }
-
-//     @Override
-//     public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-//             Display display = Display.getDefault();
-//             if (display != null)
-//                     for (String path : imageCache.keySet()) {
-//                             for (Image image : imageCache.get(path).values()) {
-//                                     display.syncExec(() -> image.dispose());
-//                             }
-//                     }
-//             super.destroy(bundleContext, properties);
-//     }
-
-}
index 2389755432e6ccc6b381543f2cbab3a3fe45cd37..9c94da8f34c2de11bac5ab89c34f8980ca464f14 100644 (file)
@@ -302,6 +302,8 @@ public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint,
                                        continue eventLoop;
                                }
                        }
+                       if (serverPushSession != null)
+                               serverPushSession.stop();
                        if (!display.isDisposed())
                                display.dispose();
                }
index 72e17a22d7ea73f197cfd1be56d6af930e8567b0..698fe2080486b3efbd119ccfd5f8021dfa75151b 100644 (file)
@@ -4,17 +4,21 @@ import java.util.Locale;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSessionBindingListener;
 
 import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.service.UISession;
 import org.eclipse.swt.widgets.Display;
 
 /** Singleton class providing single sources infos about the UI context. */
 public class UiContext {
        /** Can be null, thus indicating that we are not in a web context. */
+       @Deprecated
        public static HttpServletRequest getHttpRequest() {
                return RWT.getRequest();
        }
 
+       @Deprecated
        public static HttpServletResponse getHttpResponse() {
                return RWT.getResponse();
        }
@@ -49,6 +53,11 @@ public class UiContext {
                display.setData(key, value);
        }
 
+       public static void killDisplay(Display display) {
+               UISession uiSession = RWT.getUISession(display);
+               ((HttpSessionBindingListener) uiSession).valueUnbound(null);
+       }
+
        private static Display getDisplay() {
                return Display.getCurrent();
        }
index 20163cffa927732b598df6af4a508a8d2411430c..a85d8914dd89bd04bd26ea4b50fcd9c9c207ac75 100644 (file)
@@ -10,10 +10,12 @@ import org.eclipse.swt.widgets.Display;
 /** Singleton class providing single sources infos about the UI context. */
 public class UiContext {
 
+       @Deprecated
        public static HttpServletRequest getHttpRequest() {
                return null;
        }
 
+       @Deprecated
        public static HttpServletResponse getHttpResponse() {
                return null;
        }
@@ -42,6 +44,10 @@ public class UiContext {
                display.setData(key, value);
        }
 
+       public static void killDisplay(Display display) {
+               display.dispose();
+       }
+
        private static Display getDisplay() {
                return Display.getCurrent();
        }