29e7bbfda850e5e833dc077cf5fd3a1a1cbcede1
[policy/common.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2019-2020 Nordix Foundation.
7  * Modifications Copyright (C) 2020 Bell Canada. All rights reserved.
8  * ================================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.common.endpoints.http.server.internal;
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.UserStore;
31 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
32 import org.eclipse.jetty.server.CustomRequestLog;
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.Slf4jRequestLogWriter;
39 import org.eclipse.jetty.servlet.FilterHolder;
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.aaf.cadi.filter.CadiFilter;
45 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
46 import org.onap.policy.common.gson.annotation.GsonJsonIgnore;
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"; //NOSONAR
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"; //NOSONAR
62
63     /**
64      * Logger.
65      */
66     private static Logger logger = LoggerFactory.getLogger(JettyServletServer.class);
67
68     private static final String NOT_SUPPORTED = " is not supported on this type of jetty server";
69
70     /**
71      * Server name.
72      */
73     protected final String name;
74
75     /**
76      * Server host address.
77      */
78     protected final String host;
79
80     /**
81      * Server port to bind.
82      */
83     protected final int port;
84
85     /**
86      * Server auth user name.
87      */
88     protected String user;
89
90     /**
91      * Server auth password name.
92      */
93     protected String password;
94
95     /**
96      * Server base context path.
97      */
98     protected final String contextPath;
99
100     /**
101      * Embedded jetty server.
102      */
103     protected final Server jettyServer;
104
105     /**
106      * Servlet context.
107      */
108     protected final ServletContextHandler context;
109
110     /**
111      * Jetty connector.
112      */
113     protected final ServerConnector connector;
114
115     /**
116      * Jetty thread.
117      */
118     protected Thread jettyThread;
119
120     /**
121      * Start condition.
122      */
123     protected Object startCondition = new Object();
124
125     /**
126      * Constructor.
127      *
128      * @param name server name
129      * @param host server host
130      * @param port server port
131      * @param contextPath context path
132      *
133      * @throws IllegalArgumentException if invalid parameters are passed in
134      */
135     protected JettyServletServer(String name, boolean https, String host, int port, String contextPath) {
136         String srvName = name;
137
138         if (srvName == null || srvName.isEmpty()) {
139             srvName = "http-" + port;
140         }
141
142         if (port <= 0 || port >= 65535) {
143             throw new IllegalArgumentException("Invalid Port provided: " + port);
144         }
145
146         String srvHost = host;
147         if (srvHost == null || srvHost.isEmpty()) {
148             srvHost = "localhost";
149         }
150
151         String ctxtPath = contextPath;
152         if (ctxtPath == null || ctxtPath.isEmpty()) {
153             ctxtPath = "/";
154         }
155
156         this.name = srvName;
157
158         this.host = srvHost;
159         this.port = port;
160
161         this.contextPath = ctxtPath;
162
163         this.context = new ServletContextHandler(ServletContextHandler.SESSIONS);
164         this.context.setContextPath(ctxtPath);
165
166         this.jettyServer = new Server();
167
168         CustomRequestLog requestLog =
169                         new CustomRequestLog(new Slf4jRequestLogWriter(), CustomRequestLog.EXTENDED_NCSA_FORMAT);
170         this.jettyServer.setRequestLog(requestLog);
171
172         if (https) {
173             this.connector = httpsConnector();
174         } else {
175             this.connector = httpConnector();
176         }
177
178         this.connector.setName(srvName);
179         this.connector.setReuseAddress(true);
180         this.connector.setPort(port);
181         this.connector.setHost(srvHost);
182
183         this.jettyServer.addConnector(this.connector);
184         this.jettyServer.setHandler(context);
185     }
186
187     protected JettyServletServer(String name, String host, int port, String contextPath) {
188         this(name, false, host, port, contextPath);
189     }
190
191     @Override
192     public void addFilterClass(String filterPath, String filterClass) {
193         if (filterClass == null || filterClass.isEmpty()) {
194             throw new IllegalArgumentException("No filter class provided");
195         }
196
197         String tempFilterPath = filterPath;
198         if (filterPath == null || filterPath.isEmpty()) {
199             tempFilterPath = "/*";
200         }
201
202         context.addFilter(filterClass, tempFilterPath, EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
203     }
204
205     /**
206      * Returns the https connector.
207      *
208      * @return the server connector
209      */
210     public ServerConnector httpsConnector() {
211         SslContextFactory sslContextFactory = new SslContextFactory.Server();
212
213         String keyStore = System.getProperty(SYSTEM_KEYSTORE_PROPERTY_NAME);
214         if (keyStore != null) {
215             sslContextFactory.setKeyStorePath(keyStore);
216
217             String ksPassword = System.getProperty(SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME);
218             if (ksPassword != null) {
219                 sslContextFactory.setKeyStorePassword(ksPassword);
220             }
221         }
222
223         String trustStore = System.getProperty(SYSTEM_TRUSTSTORE_PROPERTY_NAME);
224         if (trustStore != null) {
225             sslContextFactory.setTrustStorePath(trustStore);
226
227             String tsPassword = System.getProperty(SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME);
228             if (tsPassword != null) {
229                 sslContextFactory.setTrustStorePassword(tsPassword);
230             }
231         }
232
233         HttpConfiguration https = new HttpConfiguration();
234         https.addCustomizer(new SecureRequestCustomizer());
235
236         return new ServerConnector(jettyServer, sslContextFactory, new HttpConnectionFactory(https));
237     }
238
239     public ServerConnector httpConnector() {
240         return new ServerConnector(this.jettyServer);
241     }
242
243     @Override
244     public void setAafAuthentication(String filterPath) {
245         this.addFilterClass(filterPath, CadiFilter.class.getName());
246     }
247
248     @Override
249     public boolean isAaf() {
250         for (FilterHolder filter : context.getServletHandler().getFilters()) {
251             if (CadiFilter.class.getName().equals(filter.getClassName())) {
252                 return true;
253             }
254         }
255         return false;
256     }
257
258     @Override
259     public void setBasicAuthentication(String user, String password, String servletPath) {
260         String srvltPath = servletPath;
261
262         if (user == null || user.isEmpty() || password == null || password.isEmpty()) {
263             throw new IllegalArgumentException("Missing user and/or password");
264         }
265
266         if (srvltPath == null || srvltPath.isEmpty()) {
267             srvltPath = "/*";
268         }
269
270         final HashLoginService hashLoginService = new HashLoginService();
271         final UserStore userStore = new UserStore();
272         userStore.addUser(user, Credential.getCredential(password), new String[] {"user"});
273         hashLoginService.setUserStore(userStore);
274         hashLoginService.setName(this.connector.getName() + "-login-service");
275
276         Constraint constraint = new Constraint();
277         constraint.setName(Constraint.__BASIC_AUTH);
278         constraint.setRoles(new String[] {"user"});
279         constraint.setAuthenticate(true);
280
281         ConstraintMapping constraintMapping = new ConstraintMapping();
282         constraintMapping.setConstraint(constraint);
283         constraintMapping.setPathSpec(srvltPath);
284
285         ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
286         securityHandler.setAuthenticator(new BasicAuthenticator());
287         securityHandler.setRealmName(this.connector.getName() + "-realm");
288         securityHandler.addConstraintMapping(constraintMapping);
289         securityHandler.setLoginService(hashLoginService);
290
291         this.context.setSecurityHandler(securityHandler);
292
293         this.user = user;
294         this.password = password;
295     }
296
297     /**
298      * jetty server execution.
299      */
300     @Override
301     public void run() {
302         try {
303             logger.info("{}: STARTING", this);
304
305             this.jettyServer.start();
306
307             if (logger.isTraceEnabled()) {
308                 logger.trace("{}: STARTED: {}", this, this.jettyServer.dump());
309             }
310
311             synchronized (this.startCondition) {
312                 this.startCondition.notifyAll();
313             }
314
315             this.jettyServer.join();
316         } catch (Exception e) {
317             logger.error("{}: error found while bringing up server", this, e);
318         }
319     }
320
321     @Override
322     public boolean waitedStart(long maxWaitTime) throws InterruptedException {
323         logger.info("{}: WAITED-START", this);
324
325         if (maxWaitTime < 0) {
326             throw new IllegalArgumentException("max-wait-time cannot be negative");
327         }
328
329         long pendingWaitTime = maxWaitTime;
330
331         if (!this.start()) {
332             return false;
333         }
334
335         synchronized (this.startCondition) {
336
337             while (!this.jettyServer.isRunning()) {
338                 try {
339                     long startTs = System.currentTimeMillis();
340
341                     this.startCondition.wait(pendingWaitTime);
342
343                     if (maxWaitTime == 0) {
344                         /* spurious notification */
345                         continue;
346                     }
347
348                     long endTs = System.currentTimeMillis();
349                     pendingWaitTime = pendingWaitTime - (endTs - startTs);
350
351                     logger.info("{}: pending time is {} ms.", this, pendingWaitTime);
352
353                     if (pendingWaitTime <= 0) {
354                         return false;
355                     }
356
357                 } catch (InterruptedException e) {
358                     logger.warn("{}: waited-start has been interrupted", this);
359                     throw e;
360                 }
361             }
362
363             return this.jettyServer.isRunning();
364         }
365     }
366
367     @Override
368     public boolean start() {
369         logger.info("{}: STARTING", this);
370
371         synchronized (this) {
372             if (jettyThread == null || !this.jettyThread.isAlive()) {
373
374                 this.jettyThread = new Thread(this);
375                 this.jettyThread.setName(this.name + "-" + this.port);
376                 this.jettyThread.start();
377             }
378         }
379
380         return true;
381     }
382
383     @Override
384     public boolean stop() {
385         logger.info("{}: STOPPING", this);
386
387         synchronized (this) {
388             if (jettyThread == null) {
389                 return true;
390             }
391
392             if (!jettyThread.isAlive()) {
393                 this.jettyThread = null;
394             }
395
396             try {
397                 this.connector.stop();
398             } catch (Exception e) {
399                 logger.error("{}: error while stopping management server", this, e);
400             }
401
402             try {
403                 this.jettyServer.stop();
404             } catch (Exception e) {
405                 logger.error("{}: error while stopping management server", this, e);
406                 return false;
407             }
408
409             Thread.yield();
410         }
411
412         return true;
413     }
414
415     @Override
416     public void shutdown() {
417         logger.info("{}: SHUTTING DOWN", this);
418
419         this.stop();
420
421         Thread jettyThreadCopy;
422         synchronized (this) {
423             if ((jettyThreadCopy = this.jettyThread) == null) {
424                 return;
425             }
426         }
427
428         if (jettyThreadCopy.isAlive()) {
429             try {
430                 jettyThreadCopy.join(2000L);
431             } catch (InterruptedException e) {
432                 logger.warn("{}: error while shutting down management server", this);
433                 Thread.currentThread().interrupt();
434             }
435             if (!jettyThreadCopy.isInterrupted()) {
436                 try {
437                     jettyThreadCopy.interrupt();
438                 } catch (Exception e) {
439                     // do nothing
440                     logger.warn("{}: exception while shutting down (OK)", this, e);
441                 }
442             }
443         }
444
445         this.jettyServer.destroy();
446     }
447
448     @Override
449     public boolean isAlive() {
450         if (this.jettyThread != null) {
451             return this.jettyThread.isAlive();
452         }
453
454         return false;
455     }
456
457     @Override
458     public int getPort() {
459         return this.port;
460     }
461
462     /**
463      * Get name.
464      *
465      * @return the name
466      */
467     @Override
468     public String getName() {
469         return name;
470     }
471
472     /**
473      * Get host.
474      *
475      * @return the host
476      */
477     public String getHost() {
478         return host;
479     }
480
481     /**
482      * Get user.
483      *
484      * @return the user
485      */
486     public String getUser() {
487         return user;
488     }
489
490     /**
491      * Get password.
492      *
493      * @return the password
494      */
495     @GsonJsonIgnore
496     public String getPassword() {
497         return password;
498     }
499
500     @Override
501     public void setSerializationProvider(String provider) {
502         throw new UnsupportedOperationException("setSerializationProvider()" + NOT_SUPPORTED);
503     }
504
505     @Override
506     public void addServletClass(String servletPath, String restClass) {
507         throw new UnsupportedOperationException("addServletClass()" + NOT_SUPPORTED);
508     }
509
510     @Override
511     public void addServletPackage(String servletPath, String restPackage) {
512         throw new UnsupportedOperationException("addServletPackage()" + NOT_SUPPORTED);
513     }
514
515     @Override
516     public void addServletResource(String servletPath, String resourceBase) {
517         throw new UnsupportedOperationException("addServletResource()" + NOT_SUPPORTED);
518     }
519
520     @Override
521     public String toString() {
522         StringBuilder builder = new StringBuilder();
523         builder.append("JettyServer [name=").append(name).append(", host=").append(host).append(", port=").append(port)
524                 .append(", user=").append(user).append(", password=").append(password != null).append(", contextPath=")
525                 .append(contextPath).append(", jettyServer=").append(jettyServer).append(", context=")
526                 .append(this.context).append(", connector=").append(connector).append(", jettyThread=")
527                 .append(jettyThread).append("]");
528         return builder.toString();
529     }
530
531 }