+package org.argeo.cms.servlet.internal.jetty;
+
+import java.io.File;
+import java.util.Dictionary;
+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;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.websocket.server.CmsWebSocketConfigurator;
+import org.argeo.cms.websocket.server.TestEndpoint;
+import org.argeo.util.LangUtils;
+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);
+
+ final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
+
+ private CmsState cmsState;
+
+ private final BundleContext bc = FrameworkUtil.getBundle(JettyConfig.class).getBundleContext();
+
+ // private static final String JETTY_PROPERTY_PREFIX =
+ // "org.eclipse.equinox.http.jetty.";
+
+ public void start() {
+ // We need to start asynchronously so that Jetty bundle get started by lazy init
+ // due to the non-configurable behaviour of its activator
+ ForkJoinPool.commonPool().execute(() -> {
+ Dictionary<String, Object> properties = getHttpServerConfig();
+ 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();
+
+ // check initialisation
+// ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
+//
+// @Override
+// public HttpService addingService(ServiceReference<HttpService> sr) {
+// Object httpPort = sr.getProperty("http.port");
+// Object httpsPort = sr.getProperty("https.port");
+// log.info(httpPortsMsg(httpPort, httpsPort));
+// close();
+// return super.addingService(sr);
+// }
+// };
+// httpSt.open();
+ }
+
+ public void stop() {
+ try {
+ JettyConfigurator.stopServer(CmsConstants.DEFAULT);
+ } catch (Exception e) {
+ log.error("Cannot stop default Jetty server.", e);
+ }
+
+ }
+
+ public void startServer(Dictionary<String, Object> properties) {
+ // Explicitly configures Jetty so that the default server is not started by the
+ // activator of the Equinox Jetty bundle.
+ Map<String, String> config = LangUtils.dictToStringMap(properties);
+ if (!config.isEmpty()) {
+ config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS);
+
+ // TODO centralise with Jetty extender
+ Object webSocketEnabled = config.get(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty());
+ if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
+ bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
+ // config.put(WEBSOCKET_ENABLED, "true");
+ }
+ }
+
+ properties.put(Constants.SERVICE_PID, "default");
+ File jettyWorkDir = new File(bc.getDataFile(""), "jettywork"); //$NON-NLS-1$
+ jettyWorkDir.mkdir();
+
+// HttpServerManager serverManager = new HttpServerManager(jettyWorkDir);
+// try {
+// serverManager.updated("default", properties);
+// } catch (ConfigurationException e) {
+// // TODO Auto-generated catch block
+// e.printStackTrace();
+// }
+ Object httpPort = config.get(JettyHttpConstants.HTTP_PORT);
+ Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT);
+ log.info(httpPortsMsg(httpPort, httpsPort));
+
+// long begin = System.currentTimeMillis();
+// int tryCount = 60;
+// try {
+// while (tryCount > 0) {
+// try {
+// // FIXME deal with multiple ids
+// JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config));
+//
+// Object httpPort = config.get(JettyHttpConstants.HTTP_PORT);
+// Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT);
+// log.info(httpPortsMsg(httpPort, httpsPort));
+//
+// // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
+// // configuration is not cleaned
+// FrameworkUtil.getBundle(JettyConfigurator.class).start();
+// return;
+// } catch (IllegalStateException e) {
+// // e.printStackTrace();
+// // Jetty may not be ready
+// try {
+// Thread.sleep(1000);
+// } catch (Exception e1) {
+// // silent
+// }
+// tryCount--;
+// }
+// }
+// long duration = System.currentTimeMillis() - begin;
+// log.error("Gave up with starting Jetty server after " + (duration / 1000) + " s");
+// } catch (Exception e) {
+// log.error("Cannot start default Jetty server with config " + properties, e);
+// }
+
+ }
+
+ private String httpPortsMsg(Object httpPort, Object httpsPort) {
+ return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
+ }
+
+ /** Override the provided config with the framework properties */
+ public Dictionary<String, Object> getHttpServerConfig() {
+ String httpPort = getFrameworkProp(CmsDeployProperty.HTTP_PORT);
+ String httpsPort = getFrameworkProp(CmsDeployProperty.HTTPS_PORT);
+ /// TODO make it more generic
+ String httpHost = getFrameworkProp(CmsDeployProperty.HOST);
+// String httpsHost = getFrameworkProp(
+// JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST);
+ String webSocketEnabled = getFrameworkProp(CmsDeployProperty.WEBSOCKET_ENABLED);
+
+ final Hashtable<String, Object> props = new Hashtable<String, Object>();
+ // try {
+ if (httpPort != null || httpsPort != null) {
+ boolean httpEnabled = httpPort != null;
+ props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled);
+ boolean httpsEnabled = httpsPort != null;
+ props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled);
+
+ if (httpEnabled) {
+ props.put(JettyHttpConstants.HTTP_PORT, httpPort);
+ if (httpHost != null)
+ props.put(JettyHttpConstants.HTTP_HOST, httpHost);
+ }
+
+ if (httpsEnabled) {
+ props.put(JettyHttpConstants.HTTPS_PORT, httpsPort);
+ if (httpHost != null)
+ props.put(JettyHttpConstants.HTTPS_HOST, httpHost);
+
+ // keystore
+ props.put(JettyHttpConstants.SSL_KEYSTORETYPE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORETYPE));
+ props.put(JettyHttpConstants.SSL_KEYSTORE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORE));
+ props.put(JettyHttpConstants.SSL_PASSWORD, getFrameworkProp(CmsDeployProperty.SSL_PASSWORD));
+
+ // truststore
+ props.put(JettyHttpConstants.SSL_TRUSTSTORETYPE,
+ getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+ props.put(JettyHttpConstants.SSL_TRUSTSTORE, getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORE));
+ props.put(JettyHttpConstants.SSL_TRUSTSTOREPASSWORD,
+ getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
+
+ // client certificate authentication
+ String wantClientAuth = getFrameworkProp(CmsDeployProperty.SSL_WANTCLIENTAUTH);
+ if (wantClientAuth != null)
+ props.put(JettyHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
+ String needClientAuth = getFrameworkProp(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
+ if (needClientAuth != null)
+ props.put(JettyHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
+ }
+
+ // web socket
+ if (webSocketEnabled != null && webSocketEnabled.equals("true"))
+ props.put(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty(), true);
+
+ props.put(CmsConstants.CN, CmsConstants.DEFAULT);
+ }
+ return props;
+ }
+
+ private String getFrameworkProp(CmsDeployProperty deployProperty) {
+ return cmsState.getDeployProperty(deployProperty.getProperty());
+ }
+
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+
+}