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