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