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