From: Mathieu Baudier Date: Tue, 19 Dec 2023 06:07:16 +0000 (+0100) Subject: Merge tag 'v2.3.27' into testing X-Git-Tag: v2.1.115~1 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=1a65fa72abac6a458139240efa281138aac9ef2b;hp=42ac56b990d8e9a4c6bb38d93264bfeec26bb9fb;p=lgpl%2Fargeo-commons.git Merge tag 'v2.3.27' into testing --- diff --git a/Makefile b/Makefile index ec06bd782..9011b2dc0 100644 --- 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 \ diff --git a/Makefile-rcp.mk b/Makefile-rcp.mk index 2cadd3ae7..1fb93728c 100644 --- a/Makefile-rcp.mk +++ b/Makefile-rcp.mk @@ -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 \ diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java index 96a09a91b..3375bcde5 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java @@ -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 diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java index 2103e4989..fc8c9e0bc 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java @@ -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; + }; + } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java index 121e4bdcb..b231bea99 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java @@ -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 index 672722946..000000000 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java +++ /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 index 29a3137bb..000000000 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java +++ /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 extractFrom(Set 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 index d18637d3f..000000000 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java +++ /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 extractFrom(Set 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 index 09f17ae02..000000000 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java +++ /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 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() { -// -// @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 index 30de616a2..000000000 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java +++ /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 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 index c355ecd8d..000000000 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java +++ /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 extractFrom(Set 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 index 1541b4f29..000000000 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java +++ /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 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 getRoles() { -// return roles; -// } -// -// public void setRoles(Set 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 index 1405737ee..000000000 --- a/org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS integration (JSON, web services). */ -package org.argeo.cms.integration; \ No newline at end of file diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java index defc59efc..c0dc97754 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java @@ -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; diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java index a8466fee2..b385bf84d 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java @@ -22,7 +22,6 @@ public class StatusHandler implements WebsocketEndpoints, HttpHandler { Set> 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 index f0c7fca3a..000000000 --- a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java +++ /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 eventHandlerSr; - - // json - private ObjectMapper objectMapper = new ObjectMapper(); - - private WebSocketView view; - - @OnOpen - public void onOpen(Session session, EndpointConfig endpointConfig) { - Map> 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 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 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); - } - } -} diff --git a/org.argeo.cms.jshell/src/org/argeo/cms/jshell/CmsJShell.java b/org.argeo.cms.jshell/src/org/argeo/cms/jshell/CmsJShell.java index d84ce7212..f08a2a57e 100644 --- a/org.argeo.cms.jshell/src/org/argeo/cms/jshell/CmsJShell.java +++ b/org.argeo.cms.jshell/src/org/argeo/cms/jshell/CmsJShell.java @@ -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); } } diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java index b0b348d9c..8e6db220c 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java @@ -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 { diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java index 1e64fe075..4b4fe14ac 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java @@ -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 diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java index d6037ba8d..de2fa4486 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java @@ -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; diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java index 551e54e05..c876f3dbe 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java @@ -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; diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java index 98975c3c8..74f0aaed3 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java @@ -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 diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java index b2a472b44..d5fec4a83 100644 --- a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java +++ b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java @@ -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; diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java index 8a6def33a..98bb04544 100644 --- a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java +++ b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java @@ -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 index 852cb5221..000000000 --- a/org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java +++ /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); - } -} diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java index 84e471aaf..f9ced18df 100644 --- a/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java +++ b/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java @@ -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(" cmsSessionsByUuid = new HashMap<>(); private Map 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) { } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java index f9a1dc368..e1c420b82 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java @@ -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; } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java index 943c06f4e..b35b4be09 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java @@ -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); diff --git a/org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java b/org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java index 5fbef6b31..f4242e6c2 100644 --- a/org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java @@ -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); diff --git a/org.argeo.init/src/org/argeo/init/Service.java b/org.argeo.init/src/org/argeo/init/Service.java index c63fdcd37..b080a7513 100644 --- a/org.argeo.init/src/org/argeo/init/Service.java +++ b/org.argeo.init/src/org/argeo/init/Service.java @@ -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 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) 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); + } } diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Component.java b/org.argeo.init/src/org/argeo/init/a2/A2Component.java index cc2f56471..894270630 100644 --- a/org.argeo.init/src/org/argeo/init/a2/A2Component.java +++ b/org.argeo.init/src/org/argeo/init/a2/A2Component.java @@ -28,10 +28,11 @@ public class A2Component implements Comparable { } 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) { diff --git a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java b/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java index 151b0023f..921992da3 100644 --- a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java +++ b/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java @@ -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(".")) { diff --git a/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java b/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java index 0064ab9ed..7f1133f67 100644 --- a/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java +++ b/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java @@ -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() + ")"); + + } } diff --git a/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java b/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java index 6a0836bdf..289870abc 100644 --- a/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java +++ b/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java @@ -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) { diff --git a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java index c2ce21528..ff602ad51 100644 --- a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java +++ b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java @@ -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> { */ 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> { 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> { 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> { // silent } } + + if (fileOut != null) { + try { + fileOut.close(); + } catch (Exception e) { + // silent + } + } } private Level computeApplicableLevel(String name) { diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java index 2e96db3b3..f5b260cab 100644 --- a/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java +++ b/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java @@ -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 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); } } } diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd b/osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd index 2ced2a26c..c0d2b8caf 100644 --- a/osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd +++ b/osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd @@ -1,2 +1,6 @@ Service-Component: \ OSGI-INF/equinoxJettyServer.xml,\ + +Import-Package:\ +org.eclipse.jetty.session,\ +* diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java index e6595a05e..cd4d5cee8 100644 --- a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java +++ b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java @@ -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. */ diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java index 2cd600152..71236ee1d 100644 --- a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java +++ b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java @@ -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 serverSt = new ServiceTracker( - bc, ServerContainer.class, null) { - - @Override - public ServerContainer addingService(ServiceReference reference) { - ServerContainer serverContainer = super.addingService(reference); - - BundleContext bc = reference.getBundle().getBundleContext(); - ServiceReference 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 serverSt = new ServiceTracker( +// bc, ServerContainer.class, null) { +// +// @Override +// public ServerContainer addingService(ServiceReference reference) { +// ServerContainer serverContainer = super.addingService(reference); +// +// BundleContext bc = reference.getBundle().getBundleContext(); +// ServiceReference 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(bc, HttpService.class, null) { diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java index 7be23fc0f..e10dcf0d1 100644 --- a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java +++ b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java @@ -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; diff --git a/sdk/argeo-build b/sdk/argeo-build index f2ebcc21e..cabcc3462 160000 --- a/sdk/argeo-build +++ b/sdk/argeo-build @@ -1 +1 @@ -Subproject commit f2ebcc21eecda1ee5db65700cf16f4833a7190b0 +Subproject commit cabcc3462226b71849ca42301c21e05b63f150c2 diff --git a/sdk/branches/unstable.bnd b/sdk/branches/unstable.bnd index 16c51ab97..b8300134b 100644 --- a/sdk/branches/unstable.bnd +++ b/sdk/branches/unstable.bnd @@ -1,6 +1,6 @@ major=2 minor=3 -micro=23 +micro=27 qualifier= Bundle-Copyright= \ diff --git a/sdk/cms-e4-rap.properties b/sdk/cms-e4-rap.properties index 708ab5340..4fe2113be 100644 --- a/sdk/cms-e4-rap.properties +++ b/sdk/cms-e4-rap.properties @@ -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,\ diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java index 06bb9be37..127be0856 100644 --- a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java @@ -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); + } + } + } + }; + } } diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java index e0f63e45e..d02ad3aec 100644 --- a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java +++ b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java @@ -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 index 74de83e09..000000000 --- a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java +++ /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> imageDataCache = Collections.synchronizedMap(new HashMap<>()); - - private Map 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> imageCache = (Map>) display - .getData(IMAGE_CACHE_KEY); - if (imageCache == null) - display.setData(IMAGE_CACHE_KEY, new HashMap>()); - imageCache = (Map>) 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 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 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); -// } - -} diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java index 238975543..9c94da8f3 100644 --- a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java +++ b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java @@ -302,6 +302,8 @@ public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint, continue eventLoop; } } + if (serverPushSession != null) + serverPushSession.stop(); if (!display.isDisposed()) display.dispose(); } diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java index 72e17a22d..698fe2080 100644 --- a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java +++ b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java @@ -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(); } diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java index 20163cffa..a85d8914d 100644 --- a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java +++ b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java @@ -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(); }