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