1 package org
.argeo
.cms
.servlet
.internal
.jetty
;
3 import java
.io
.IOException
;
5 import java
.nio
.charset
.StandardCharsets
;
6 import java
.nio
.file
.Files
;
7 import java
.nio
.file
.Path
;
8 import java
.security
.KeyStore
;
9 import java
.util
.Dictionary
;
10 import java
.util
.Hashtable
;
12 import java
.util
.concurrent
.ForkJoinPool
;
14 import javax
.websocket
.DeploymentException
;
15 import javax
.websocket
.server
.ServerContainer
;
16 import javax
.websocket
.server
.ServerEndpointConfig
;
18 import org
.argeo
.api
.cms
.CmsConstants
;
19 import org
.argeo
.api
.cms
.CmsLog
;
20 import org
.argeo
.api
.cms
.CmsState
;
21 import org
.argeo
.cms
.security
.PkiUtils
;
22 import org
.argeo
.cms
.websocket
.javax
.server
.CmsWebSocketConfigurator
;
23 import org
.argeo
.cms
.websocket
.javax
.server
.TestEndpoint
;
24 import org
.argeo
.util
.LangUtils
;
25 import org
.eclipse
.equinox
.http
.jetty
.JettyConfigurator
;
26 import org
.osgi
.framework
.BundleContext
;
27 import org
.osgi
.framework
.FrameworkUtil
;
28 import org
.osgi
.framework
.ServiceReference
;
29 import org
.osgi
.util
.tracker
.ServiceTracker
;
31 public class JettyConfig
{
32 private final static CmsLog log
= CmsLog
.getLog(JettyConfig
.class);
34 final static String CMS_JETTY_CUSTOMIZER_CLASS
= "org.argeo.equinox.jetty.CmsJettyCustomizer";
36 final static String WEBSOCKET_ENABLED
= "websocket.enabled";
38 private CmsState cmsState
;
40 private final BundleContext bc
= FrameworkUtil
.getBundle(JettyConfig
.class).getBundleContext();
43 // We need to start asynchronously so that Jetty bundle get started by lazy init
44 // due to the non-configurable behaviour of its activator
45 ForkJoinPool
.commonPool().execute(() -> {
46 Dictionary
<String
, ?
> properties
= getHttpServerConfig();
47 startServer(properties
);
50 ServiceTracker
<ServerContainer
, ServerContainer
> serverSt
= new ServiceTracker
<ServerContainer
, ServerContainer
>(
51 bc
, ServerContainer
.class, null) {
54 public ServerContainer
addingService(ServiceReference
<ServerContainer
> reference
) {
55 ServerContainer serverContainer
= super.addingService(reference
);
57 BundleContext bc
= reference
.getBundle().getBundleContext();
58 ServiceReference
<ServerEndpointConfig
.Configurator
> srConfigurator
= bc
59 .getServiceReference(ServerEndpointConfig
.Configurator
.class);
60 ServerEndpointConfig
.Configurator endpointConfigurator
= bc
.getService(srConfigurator
);
61 ServerEndpointConfig config
= ServerEndpointConfig
.Builder
62 .create(TestEndpoint
.class, "/ws/test/events/").configurator(endpointConfigurator
).build();
64 serverContainer
.addEndpoint(config
);
65 } catch (DeploymentException e
) {
66 throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e
);
68 return serverContainer
;
74 // check initialisation
75 // ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
78 // public HttpService addingService(ServiceReference<HttpService> sr) {
79 // Object httpPort = sr.getProperty("http.port");
80 // Object httpsPort = sr.getProperty("https.port");
81 // log.info(httpPortsMsg(httpPort, httpsPort));
83 // return super.addingService(sr);
91 JettyConfigurator
.stopServer(CmsConstants
.DEFAULT
);
92 } catch (Exception e
) {
93 log
.error("Cannot stop default Jetty server.", e
);
98 public void startServer(Dictionary
<String
, ?
> properties
) {
99 // Explicitly configures Jetty so that the default server is not started by the
100 // activator of the Equinox Jetty bundle.
101 Map
<String
, String
> config
= LangUtils
.dictToStringMap(properties
);
102 if (!config
.isEmpty()) {
103 config
.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS
);
105 // TODO centralise with Jetty extender
106 Object webSocketEnabled
= config
.get(WEBSOCKET_ENABLED
);
107 if (webSocketEnabled
!= null && webSocketEnabled
.toString().equals("true")) {
108 bc
.registerService(ServerEndpointConfig
.Configurator
.class, new CmsWebSocketConfigurator(), null);
109 config
.put(WEBSOCKET_ENABLED
, "true");
115 tryGettyJetty
: while (tryCount
> 0) {
117 // FIXME deal with multiple ids
118 JettyConfigurator
.startServer(CmsConstants
.DEFAULT
, new Hashtable
<>(config
));
120 Object httpPort
= config
.get(InternalHttpConstants
.HTTP_PORT
);
121 Object httpsPort
= config
.get(InternalHttpConstants
.HTTPS_PORT
);
122 log
.info(httpPortsMsg(httpPort
, httpsPort
));
124 // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
125 // configuration is not cleaned
126 FrameworkUtil
.getBundle(JettyConfigurator
.class).start();
128 } catch (IllegalStateException e
) {
129 // e.printStackTrace();
130 // Jetty may not be ready
133 } catch (Exception e1
) {
139 } catch (Exception e
) {
140 log
.error("Cannot start default Jetty server with config " + properties
, e
);
145 private String
httpPortsMsg(Object httpPort
, Object httpsPort
) {
146 return (httpPort
!= null ?
"HTTP " + httpPort
+ " " : " ") + (httpsPort
!= null ?
"HTTPS " + httpsPort
: "");
149 /** Override the provided config with the framework properties */
150 public Dictionary
<String
, Object
> getHttpServerConfig() {
151 String httpPort
= getFrameworkProp("org.osgi.service.http.port");
152 String httpsPort
= getFrameworkProp("org.osgi.service.http.port.secure");
153 /// TODO make it more generic
154 String httpHost
= getFrameworkProp(
155 InternalHttpConstants
.JETTY_PROPERTY_PREFIX
+ InternalHttpConstants
.HTTP_HOST
);
156 String httpsHost
= getFrameworkProp(
157 InternalHttpConstants
.JETTY_PROPERTY_PREFIX
+ InternalHttpConstants
.HTTPS_HOST
);
158 String webSocketEnabled
= getFrameworkProp(
159 InternalHttpConstants
.JETTY_PROPERTY_PREFIX
+ InternalHttpConstants
.WEBSOCKET_ENABLED
);
161 final Hashtable
<String
, Object
> props
= new Hashtable
<String
, Object
>();
163 if (httpPort
!= null || httpsPort
!= null) {
164 boolean httpEnabled
= httpPort
!= null;
165 props
.put(InternalHttpConstants
.HTTP_ENABLED
, httpEnabled
);
166 boolean httpsEnabled
= httpsPort
!= null;
167 props
.put(InternalHttpConstants
.HTTPS_ENABLED
, httpsEnabled
);
170 props
.put(InternalHttpConstants
.HTTP_PORT
, httpPort
);
171 if (httpHost
!= null)
172 props
.put(InternalHttpConstants
.HTTP_HOST
, httpHost
);
176 props
.put(InternalHttpConstants
.HTTPS_PORT
, httpsPort
);
177 if (httpsHost
!= null)
178 props
.put(InternalHttpConstants
.HTTPS_HOST
, httpsHost
);
180 // server certificate
181 Path keyStorePath
= cmsState
.getDataPath(PkiUtils
.DEFAULT_KEYSTORE_PATH
);
182 Path pemKeyPath
= cmsState
.getDataPath(PkiUtils
.DEFAULT_PEM_KEY_PATH
);
183 Path pemCertPath
= cmsState
.getDataPath(PkiUtils
.DEFAULT_PEM_CERT_PATH
);
184 String keyStorePasswordStr
= getFrameworkProp(
185 InternalHttpConstants
.JETTY_PROPERTY_PREFIX
+ InternalHttpConstants
.SSL_PASSWORD
);
186 char[] keyStorePassword
;
187 if (keyStorePasswordStr
== null)
188 keyStorePassword
= "changeit".toCharArray();
190 keyStorePassword
= keyStorePasswordStr
.toCharArray();
192 // if PEM files both exists, update the PKCS12 file
193 if (Files
.exists(pemCertPath
) && Files
.exists(pemKeyPath
)) {
194 // TODO check certificate update time? monitor changes?
195 KeyStore keyStore
= PkiUtils
.getKeyStore(keyStorePath
, keyStorePassword
, PkiUtils
.PKCS12
);
196 try (Reader key
= Files
.newBufferedReader(pemKeyPath
, StandardCharsets
.US_ASCII
);
197 Reader cert
= Files
.newBufferedReader(pemCertPath
, StandardCharsets
.US_ASCII
);) {
198 PkiUtils
.loadPem(keyStore
, key
, keyStorePassword
, cert
);
199 PkiUtils
.saveKeyStore(keyStorePath
, keyStorePassword
, keyStore
);
200 if (log
.isDebugEnabled())
201 log
.debug("PEM certificate stored in " + keyStorePath
);
202 } catch (IOException e
) {
203 log
.error("Cannot read PEM files " + pemKeyPath
+ " and " + pemCertPath
, e
);
207 if (!Files
.exists(keyStorePath
))
208 PkiUtils
.createSelfSignedKeyStore(keyStorePath
, keyStorePassword
, PkiUtils
.PKCS12
);
209 props
.put(InternalHttpConstants
.SSL_KEYSTORETYPE
, PkiUtils
.PKCS12
);
210 props
.put(InternalHttpConstants
.SSL_KEYSTORE
, keyStorePath
.toString());
211 props
.put(InternalHttpConstants
.SSL_PASSWORD
, new String(keyStorePassword
));
213 // props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
214 // props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
215 // props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
217 // client certificate authentication
218 String wantClientAuth
= getFrameworkProp(
219 InternalHttpConstants
.JETTY_PROPERTY_PREFIX
+ InternalHttpConstants
.SSL_WANTCLIENTAUTH
);
220 if (wantClientAuth
!= null)
221 props
.put(InternalHttpConstants
.SSL_WANTCLIENTAUTH
, Boolean
.parseBoolean(wantClientAuth
));
222 String needClientAuth
= getFrameworkProp(
223 InternalHttpConstants
.JETTY_PROPERTY_PREFIX
+ InternalHttpConstants
.SSL_NEEDCLIENTAUTH
);
224 if (needClientAuth
!= null)
225 props
.put(InternalHttpConstants
.SSL_NEEDCLIENTAUTH
, Boolean
.parseBoolean(needClientAuth
));
229 if (webSocketEnabled
!= null && webSocketEnabled
.equals("true"))
230 props
.put(InternalHttpConstants
.WEBSOCKET_ENABLED
, true);
232 props
.put(CmsConstants
.CN
, CmsConstants
.DEFAULT
);
237 private String
getFrameworkProp(String key
) {
238 return cmsState
.getDeployProperty(key
);
241 public void setCmsState(CmsState cmsState
) {
242 this.cmsState
= cmsState
;