a4cc9b5f0c2d6502f391bf633c0ddecddce0e140
[policy/common.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.common.endpoints.http.server.internal;
22
23 import com.fasterxml.jackson.annotation.JsonIgnore;
24
25 import org.eclipse.jetty.security.ConstraintMapping;
26 import org.eclipse.jetty.security.ConstraintSecurityHandler;
27 import org.eclipse.jetty.security.HashLoginService;
28 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
29 import org.eclipse.jetty.server.HttpConfiguration;
30 import org.eclipse.jetty.server.HttpConnectionFactory;
31 import org.eclipse.jetty.server.SecureRequestCustomizer;
32 import org.eclipse.jetty.server.Server;
33 import org.eclipse.jetty.server.ServerConnector;
34 import org.eclipse.jetty.server.Slf4jRequestLog;
35 import org.eclipse.jetty.servlet.ServletContextHandler;
36 import org.eclipse.jetty.util.security.Constraint;
37 import org.eclipse.jetty.util.security.Credential;
38 import org.eclipse.jetty.util.ssl.SslContextFactory;
39 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * Http Server implementation using Embedded Jetty
45  */
46 public abstract class JettyServletServer implements HttpServletServer, Runnable {
47
48     /**
49      * Keystore/Truststore system property names
50      */
51     public static final String SYSTEM_KEYSTORE_PROPERTY_NAME = "javax.net.ssl.keyStore";
52     public static final String SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME = "javax.net.ssl.keyStorePassword";
53     public static final String SYSTEM_TRUSTSTORE_PROPERTY_NAME = "javax.net.ssl.trustStore";
54     public static final String SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME = "javax.net.ssl.trustStorePassword";
55
56     /**
57      * Logger
58      */
59     private static Logger logger = LoggerFactory.getLogger(JettyServletServer.class);
60
61     /**
62      * server name
63      */
64     protected final String name;
65
66     /**
67      * server host address
68      */
69     protected final String host;
70
71     /**
72      * server port to bind
73      */
74     protected final int port;
75
76     /**
77      * server auth user name
78      */
79     protected String user;
80
81     /**
82      * server auth password name
83      */
84     protected String password;
85
86     /**
87      * server base context path
88      */
89     protected final String contextPath;
90
91     /**
92      * embedded jetty server
93      */
94     protected final Server jettyServer;
95
96     /**
97      * servlet context
98      */
99     protected final ServletContextHandler context;
100
101     /**
102      * jetty connector
103      */
104     protected final ServerConnector connector;
105
106     /**
107      * jetty thread
108      */
109     protected volatile Thread jettyThread;
110
111     /**
112      * start condition
113      */
114     protected Object startCondition = new Object();
115
116     /**
117      * constructor
118      * 
119      * @param name server name
120      * @param host server host
121      * @param port server port
122      * @param contextPath context path
123      * 
124      * @throws IllegalArgumentException if invalid parameters are passed in
125      */
126     public JettyServletServer(String name, boolean https, String host, int port, String contextPath) {
127         String srvName = name;
128         String srvHost = host;
129         String ctxtPath = contextPath;
130
131         if (srvName == null || srvName.isEmpty()) {
132             srvName = "http-" + port;
133         }
134
135         if (port <= 0 || port >= 65535) {
136             throw new IllegalArgumentException("Invalid Port provided: " + port);
137         }
138
139         if (srvHost == null || srvHost.isEmpty()) {
140             srvHost = "localhost";
141         }
142
143         if (ctxtPath == null || ctxtPath.isEmpty()) {
144             ctxtPath = "/";
145         }
146
147         this.name = srvName;
148
149         this.host = srvHost;
150         this.port = port;
151
152         this.contextPath = ctxtPath;
153
154         this.context = new ServletContextHandler(ServletContextHandler.SESSIONS);
155         this.context.setContextPath(ctxtPath);
156
157         this.jettyServer = new Server();
158         this.jettyServer.setRequestLog(new Slf4jRequestLog());
159
160         if (https)
161             this.connector = httpsConnector();
162         else
163             this.connector = httpConnector();
164
165         this.connector.setName(srvName);
166         this.connector.setReuseAddress(true);
167         this.connector.setPort(port);
168         this.connector.setHost(srvHost);
169
170         this.jettyServer.addConnector(this.connector);
171         this.jettyServer.setHandler(context);
172     }
173
174     public JettyServletServer(String name, String host, int port, String contextPath) {
175         this(name, false, host, port, contextPath);
176     }
177
178     public ServerConnector httpsConnector() {
179         SslContextFactory sslContextFactory = new SslContextFactory();
180
181         String keyStore = System.getProperty(SYSTEM_KEYSTORE_PROPERTY_NAME);
182         if (keyStore != null) {
183             sslContextFactory.setKeyStorePath(keyStore);
184
185             String ksPassword = System.getProperty(SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME);
186             if (ksPassword != null)
187                 sslContextFactory.setKeyStorePassword(ksPassword);
188         }
189
190         String trustStore = System.getProperty(SYSTEM_TRUSTSTORE_PROPERTY_NAME);
191         if (trustStore != null) {
192             sslContextFactory.setTrustStorePath(trustStore);
193
194             String tsPassword = System.getProperty(SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME);
195             if (tsPassword != null)
196                 sslContextFactory.setTrustStorePassword(tsPassword);
197         }
198
199         HttpConfiguration https = new HttpConfiguration();
200         https.addCustomizer(new SecureRequestCustomizer());
201
202         return new ServerConnector(jettyServer, sslContextFactory, new HttpConnectionFactory(https));
203     }
204
205     public ServerConnector httpConnector() {
206         return new ServerConnector(this.jettyServer);
207     }
208
209     @Override
210     public void setBasicAuthentication(String user, String password, String servletPath) {
211         String srvltPath = servletPath;
212
213         if (user == null || user.isEmpty() || password == null || password.isEmpty()) {
214             throw new IllegalArgumentException("Missing user and/or password");
215         }
216
217         if (srvltPath == null || srvltPath.isEmpty()) {
218             srvltPath = "/*";
219         }
220
221         HashLoginService hashLoginService = new HashLoginService();
222         hashLoginService.putUser(user, Credential.getCredential(password), new String[] {"user"});
223         hashLoginService.setName(this.connector.getName() + "-login-service");
224
225         Constraint constraint = new Constraint();
226         constraint.setName(Constraint.__BASIC_AUTH);
227         constraint.setRoles(new String[] {"user"});
228         constraint.setAuthenticate(true);
229
230         ConstraintMapping constraintMapping = new ConstraintMapping();
231         constraintMapping.setConstraint(constraint);
232         constraintMapping.setPathSpec(srvltPath);
233
234         ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
235         securityHandler.setAuthenticator(new BasicAuthenticator());
236         securityHandler.setRealmName(this.connector.getName() + "-realm");
237         securityHandler.addConstraintMapping(constraintMapping);
238         securityHandler.setLoginService(hashLoginService);
239
240         this.context.setSecurityHandler(securityHandler);
241
242         this.user = user;
243         this.password = password;
244     }
245
246     /**
247      * jetty server execution
248      */
249     @Override
250     public void run() {
251         try {
252             logger.info("{}: STARTING", this);
253
254             this.jettyServer.start();
255
256             if (logger.isInfoEnabled()) {
257                 logger.info("{}: STARTED: {}", this, this.jettyServer.dump());
258             }
259
260             synchronized (this.startCondition) {
261                 this.startCondition.notifyAll();
262             }
263
264             this.jettyServer.join();
265         } catch (Exception e) {
266             logger.error("{}: error found while bringing up server", this, e);
267         }
268     }
269
270     @Override
271     public boolean waitedStart(long maxWaitTime) throws InterruptedException {
272         logger.info("{}: WAITED-START", this);
273
274         if (maxWaitTime < 0) {
275             throw new IllegalArgumentException("max-wait-time cannot be negative");
276         }
277
278         long pendingWaitTime = maxWaitTime;
279
280         if (!this.start()) {
281             return false;
282         }
283
284         synchronized (this.startCondition) {
285
286             while (!this.jettyServer.isRunning()) {
287                 try {
288                     long startTs = System.currentTimeMillis();
289
290                     this.startCondition.wait(pendingWaitTime);
291
292                     if (maxWaitTime == 0) {
293                         /* spurious notification */
294                         continue;
295                     }
296
297                     long endTs = System.currentTimeMillis();
298                     pendingWaitTime = pendingWaitTime - (endTs - startTs);
299
300                     logger.info("{}: pending time is {} ms.", this, pendingWaitTime);
301
302                     if (pendingWaitTime <= 0) {
303                         return false;
304                     }
305
306                 } catch (InterruptedException e) {
307                     logger.warn("{}: waited-start has been interrupted", this);
308                     throw e;
309                 }
310             }
311
312             return this.jettyServer.isRunning();
313         }
314     }
315
316     @Override
317     public boolean start() {
318         logger.info("{}: STARTING", this);
319
320         synchronized (this) {
321             if (jettyThread == null || !this.jettyThread.isAlive()) {
322
323                 this.jettyThread = new Thread(this);
324                 this.jettyThread.setName(this.name + "-" + this.port);
325                 this.jettyThread.start();
326             }
327         }
328
329         return true;
330     }
331
332     @Override
333     public boolean stop() {
334         logger.info("{}: STOPPING", this);
335
336         synchronized (this) {
337             if (jettyThread == null) {
338                 return true;
339             }
340
341             if (!jettyThread.isAlive()) {
342                 this.jettyThread = null;
343             }
344
345             try {
346                 this.connector.stop();
347             } catch (Exception e) {
348                 logger.error("{}: error while stopping management server", this, e);
349             }
350
351             try {
352                 this.jettyServer.stop();
353             } catch (Exception e) {
354                 logger.error("{}: error while stopping management server", this, e);
355                 return false;
356             }
357
358             Thread.yield();
359         }
360
361         return true;
362     }
363
364     @Override
365     public void shutdown() {
366         logger.info("{}: SHUTTING DOWN", this);
367
368         this.stop();
369
370         if (this.jettyThread == null) {
371             return;
372         }
373
374         Thread jettyThreadCopy = this.jettyThread;
375
376         if (jettyThreadCopy.isAlive()) {
377             try {
378                 jettyThreadCopy.join(2000L);
379             } catch (InterruptedException e) {
380                 logger.warn("{}: error while shutting down management server", this);
381                 Thread.currentThread().interrupt();
382             }
383             if (!jettyThreadCopy.isInterrupted()) {
384                 try {
385                     jettyThreadCopy.interrupt();
386                 } catch (Exception e) {
387                     // do nothing
388                     logger.warn("{}: exception while shutting down (OK)", this, e);
389                 }
390             }
391         }
392
393         this.jettyServer.destroy();
394     }
395
396     @Override
397     public boolean isAlive() {
398         if (this.jettyThread != null) {
399             return this.jettyThread.isAlive();
400         }
401
402         return false;
403     }
404
405     @Override
406     public int getPort() {
407         return this.port;
408     }
409
410     /**
411      * @return the name
412      */
413     public String getName() {
414         return name;
415     }
416
417     /**
418      * @return the host
419      */
420     public String getHost() {
421         return host;
422     }
423
424     /**
425      * @return the user
426      */
427     public String getUser() {
428         return user;
429     }
430
431     /**
432      * @return the password
433      */
434     @JsonIgnore
435     public String getPassword() {
436         return password;
437     }
438
439     @Override
440     public String toString() {
441         StringBuilder builder = new StringBuilder();
442         builder.append("JettyServer [name=").append(name).append(", host=").append(host).append(", port=").append(port)
443                 .append(", user=").append(user).append(", password=").append(password != null).append(", contextPath=")
444                 .append(contextPath).append(", jettyServer=").append(jettyServer).append(", context=")
445                 .append(this.context).append(", connector=").append(connector).append(", jettyThread=")
446                 .append(jettyThread).append("]");
447         return builder.toString();
448     }
449
450 }