b22a94017a65ca0691049b824dbbe8eee27a971d
[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         String srvHost = host;
131         String ctxtPath = contextPath;
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         if (srvHost == null || srvHost.isEmpty()) {
142             srvHost = "localhost";
143         }
144
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         this.connector.setName(srvName);
168         this.connector.setReuseAddress(true);
169         this.connector.setPort(port);
170         this.connector.setHost(srvHost);
171
172         this.jettyServer.addConnector(this.connector);
173         this.jettyServer.setHandler(context);
174     }
175
176     public JettyServletServer(String name, String host, int port, String contextPath) {
177         this(name, false, host, port, contextPath);
178     }
179
180     @Override
181     public void addFilterClass(String aFilterPath, String aFilterClass) {
182         if (aFilterClass == null || aFilterClass.isEmpty()) {
183             throw new IllegalArgumentException("No filter class provided");
184         }
185
186         String filterPath = aFilterPath;
187         if (aFilterPath == null || aFilterPath.isEmpty()) {
188             filterPath = "/*";
189         }
190
191         context.addFilter(aFilterClass, filterPath,
192                 EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
193     }
194
195     public ServerConnector httpsConnector() {
196         SslContextFactory sslContextFactory = new SslContextFactory();
197
198         String keyStore = System.getProperty(SYSTEM_KEYSTORE_PROPERTY_NAME);
199         if (keyStore != null) {
200             sslContextFactory.setKeyStorePath(keyStore);
201
202             String ksPassword = System.getProperty(SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME);
203             if (ksPassword != null)
204                 sslContextFactory.setKeyStorePassword(ksPassword);
205         }
206
207         String trustStore = System.getProperty(SYSTEM_TRUSTSTORE_PROPERTY_NAME);
208         if (trustStore != null) {
209             sslContextFactory.setTrustStorePath(trustStore);
210
211             String tsPassword = System.getProperty(SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME);
212             if (tsPassword != null)
213                 sslContextFactory.setTrustStorePassword(tsPassword);
214         }
215
216         HttpConfiguration https = new HttpConfiguration();
217         https.addCustomizer(new SecureRequestCustomizer());
218
219         return new ServerConnector(jettyServer, sslContextFactory, new HttpConnectionFactory(https));
220     }
221
222     public ServerConnector httpConnector() {
223         return new ServerConnector(this.jettyServer);
224     }
225
226     @Override
227     public void setBasicAuthentication(String user, String password, String servletPath) {
228         String srvltPath = servletPath;
229
230         if (user == null || user.isEmpty() || password == null || password.isEmpty()) {
231             throw new IllegalArgumentException("Missing user and/or password");
232         }
233
234         if (srvltPath == null || srvltPath.isEmpty()) {
235             srvltPath = "/*";
236         }
237
238         HashLoginService hashLoginService = new HashLoginService();
239         hashLoginService.putUser(user, Credential.getCredential(password), new String[] {"user"});
240         hashLoginService.setName(this.connector.getName() + "-login-service");
241
242         Constraint constraint = new Constraint();
243         constraint.setName(Constraint.__BASIC_AUTH);
244         constraint.setRoles(new String[] {"user"});
245         constraint.setAuthenticate(true);
246
247         ConstraintMapping constraintMapping = new ConstraintMapping();
248         constraintMapping.setConstraint(constraint);
249         constraintMapping.setPathSpec(srvltPath);
250
251         ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
252         securityHandler.setAuthenticator(new BasicAuthenticator());
253         securityHandler.setRealmName(this.connector.getName() + "-realm");
254         securityHandler.addConstraintMapping(constraintMapping);
255         securityHandler.setLoginService(hashLoginService);
256
257         this.context.setSecurityHandler(securityHandler);
258
259         this.user = user;
260         this.password = password;
261     }
262
263     /**
264      * jetty server execution
265      */
266     @Override
267     public void run() {
268         try {
269             logger.info("{}: STARTING", this);
270
271             this.jettyServer.start();
272
273             if (logger.isInfoEnabled()) {
274                 logger.info("{}: STARTED: {}", this, this.jettyServer.dump());
275             }
276
277             synchronized (this.startCondition) {
278                 this.startCondition.notifyAll();
279             }
280
281             this.jettyServer.join();
282         } catch (Exception e) {
283             logger.error("{}: error found while bringing up server", this, e);
284         }
285     }
286
287     @Override
288     public boolean waitedStart(long maxWaitTime) throws InterruptedException {
289         logger.info("{}: WAITED-START", this);
290
291         if (maxWaitTime < 0) {
292             throw new IllegalArgumentException("max-wait-time cannot be negative");
293         }
294
295         long pendingWaitTime = maxWaitTime;
296
297         if (!this.start()) {
298             return false;
299         }
300
301         synchronized (this.startCondition) {
302
303             while (!this.jettyServer.isRunning()) {
304                 try {
305                     long startTs = System.currentTimeMillis();
306
307                     this.startCondition.wait(pendingWaitTime);
308
309                     if (maxWaitTime == 0) {
310                         /* spurious notification */
311                         continue;
312                     }
313
314                     long endTs = System.currentTimeMillis();
315                     pendingWaitTime = pendingWaitTime - (endTs - startTs);
316
317                     logger.info("{}: pending time is {} ms.", this, pendingWaitTime);
318
319                     if (pendingWaitTime <= 0) {
320                         return false;
321                     }
322
323                 } catch (InterruptedException e) {
324                     logger.warn("{}: waited-start has been interrupted", this);
325                     throw e;
326                 }
327             }
328
329             return this.jettyServer.isRunning();
330         }
331     }
332
333     @Override
334     public boolean start() {
335         logger.info("{}: STARTING", this);
336
337         synchronized (this) {
338             if (jettyThread == null || !this.jettyThread.isAlive()) {
339
340                 this.jettyThread = new Thread(this);
341                 this.jettyThread.setName(this.name + "-" + this.port);
342                 this.jettyThread.start();
343             }
344         }
345
346         return true;
347     }
348
349     @Override
350     public boolean stop() {
351         logger.info("{}: STOPPING", this);
352
353         synchronized (this) {
354             if (jettyThread == null) {
355                 return true;
356             }
357
358             if (!jettyThread.isAlive()) {
359                 this.jettyThread = null;
360             }
361
362             try {
363                 this.connector.stop();
364             } catch (Exception e) {
365                 logger.error("{}: error while stopping management server", this, e);
366             }
367
368             try {
369                 this.jettyServer.stop();
370             } catch (Exception e) {
371                 logger.error("{}: error while stopping management server", this, e);
372                 return false;
373             }
374
375             Thread.yield();
376         }
377
378         return true;
379     }
380
381     @Override
382     public void shutdown() {
383         logger.info("{}: SHUTTING DOWN", this);
384
385         this.stop();
386
387         if (this.jettyThread == null) {
388             return;
389         }
390
391         Thread jettyThreadCopy = this.jettyThread;
392
393         if (jettyThreadCopy.isAlive()) {
394             try {
395                 jettyThreadCopy.join(2000L);
396             } catch (InterruptedException e) {
397                 logger.warn("{}: error while shutting down management server", this);
398                 Thread.currentThread().interrupt();
399             }
400             if (!jettyThreadCopy.isInterrupted()) {
401                 try {
402                     jettyThreadCopy.interrupt();
403                 } catch (Exception e) {
404                     // do nothing
405                     logger.warn("{}: exception while shutting down (OK)", this, e);
406                 }
407             }
408         }
409
410         this.jettyServer.destroy();
411     }
412
413     @Override
414     public boolean isAlive() {
415         if (this.jettyThread != null) {
416             return this.jettyThread.isAlive();
417         }
418
419         return false;
420     }
421
422     @Override
423     public int getPort() {
424         return this.port;
425     }
426
427     /**
428      * @return the name
429      */
430     public String getName() {
431         return name;
432     }
433
434     /**
435      * @return the host
436      */
437     public String getHost() {
438         return host;
439     }
440
441     /**
442      * @return the user
443      */
444     public String getUser() {
445         return user;
446     }
447
448     /**
449      * @return the password
450      */
451     @JsonIgnore
452     public String getPassword() {
453         return password;
454     }
455
456     @Override
457     public String toString() {
458         StringBuilder builder = new StringBuilder();
459         builder.append("JettyServer [name=").append(name).append(", host=").append(host).append(", port=").append(port)
460                 .append(", user=").append(user).append(", password=").append(password != null).append(", contextPath=")
461                 .append(contextPath).append(", jettyServer=").append(jettyServer).append(", context=")
462                 .append(this.context).append(", connector=").append(connector).append(", jettyThread=")
463                 .append(jettyThread).append("]");
464         return builder.toString();
465     }
466
467 }