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