2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017-2021 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
13 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
23 package org.onap.policy.common.endpoints.http.server.internal;
25 import java.util.EnumSet;
26 import javax.servlet.DispatcherType;
27 import lombok.ToString;
28 import org.eclipse.jetty.security.ConstraintMapping;
29 import org.eclipse.jetty.security.ConstraintSecurityHandler;
30 import org.eclipse.jetty.security.HashLoginService;
31 import org.eclipse.jetty.security.UserStore;
32 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
33 import org.eclipse.jetty.server.CustomRequestLog;
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.Slf4jRequestLogWriter;
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.onap.policy.common.gson.annotation.GsonJsonIgnore;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
52 * Http Server implementation using Embedded Jetty.
55 public abstract class JettyServletServer implements HttpServletServer, Runnable {
58 * Keystore/Truststore system property names.
60 public static final String SYSTEM_KEYSTORE_PROPERTY_NAME = "javax.net.ssl.keyStore";
61 public static final String SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME = "javax.net.ssl.keyStorePassword"; //NOSONAR
62 public static final String SYSTEM_TRUSTSTORE_PROPERTY_NAME = "javax.net.ssl.trustStore";
63 public static final String SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME = "javax.net.ssl.trustStorePassword"; //NOSONAR
68 private static Logger logger = LoggerFactory.getLogger(JettyServletServer.class);
70 private static final String NOT_SUPPORTED = " is not supported on this type of jetty server";
75 protected final String name;
78 * Server host address.
80 protected final String host;
83 * Server port to bind.
85 protected final int port;
88 * Server auth user name.
90 protected String user;
93 * Server auth password name.
95 protected String password;
98 * Server base context path.
100 protected final String contextPath;
103 * Embedded jetty server.
105 protected final Server jettyServer;
110 protected final ServletContextHandler context;
115 protected final ServerConnector connector;
120 protected Thread jettyThread;
126 protected Object startCondition = new Object();
131 * @param name server name
132 * @param host server host
133 * @param port server port
134 * @param contextPath context path
136 * @throws IllegalArgumentException if invalid parameters are passed in
138 protected JettyServletServer(String name, boolean https, String host, int port, String contextPath) {
139 String srvName = name;
141 if (srvName == null || srvName.isEmpty()) {
142 srvName = "http-" + port;
145 if (port <= 0 || port >= 65535) {
146 throw new IllegalArgumentException("Invalid Port provided: " + port);
149 String srvHost = host;
150 if (srvHost == null || srvHost.isEmpty()) {
151 srvHost = "localhost";
154 String ctxtPath = contextPath;
155 if (ctxtPath == null || ctxtPath.isEmpty()) {
164 this.contextPath = ctxtPath;
166 this.context = new ServletContextHandler(ServletContextHandler.SESSIONS);
167 this.context.setContextPath(ctxtPath);
169 this.jettyServer = new Server();
171 var requestLog = new CustomRequestLog(new Slf4jRequestLogWriter(), CustomRequestLog.EXTENDED_NCSA_FORMAT);
172 this.jettyServer.setRequestLog(requestLog);
175 this.connector = httpsConnector();
177 this.connector = httpConnector();
180 this.connector.setName(srvName);
181 this.connector.setReuseAddress(true);
182 this.connector.setPort(port);
183 this.connector.setHost(srvHost);
185 this.jettyServer.addConnector(this.connector);
186 this.jettyServer.setHandler(context);
189 protected JettyServletServer(String name, String host, int port, String contextPath) {
190 this(name, false, host, port, contextPath);
194 public void addFilterClass(String filterPath, String filterClass) {
195 if (filterClass == null || filterClass.isEmpty()) {
196 throw new IllegalArgumentException("No filter class provided");
199 String tempFilterPath = filterPath;
200 if (filterPath == null || filterPath.isEmpty()) {
201 tempFilterPath = "/*";
204 context.addFilter(filterClass, tempFilterPath, EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
208 * Returns the https connector.
210 * @return the server connector
212 public ServerConnector httpsConnector() {
213 SslContextFactory sslContextFactory = new SslContextFactory.Server();
215 String keyStore = System.getProperty(SYSTEM_KEYSTORE_PROPERTY_NAME);
216 if (keyStore != null) {
217 sslContextFactory.setKeyStorePath(keyStore);
219 String ksPassword = System.getProperty(SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME);
220 if (ksPassword != null) {
221 sslContextFactory.setKeyStorePassword(ksPassword);
225 String trustStore = System.getProperty(SYSTEM_TRUSTSTORE_PROPERTY_NAME);
226 if (trustStore != null) {
227 sslContextFactory.setTrustStorePath(trustStore);
229 String tsPassword = System.getProperty(SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME);
230 if (tsPassword != null) {
231 sslContextFactory.setTrustStorePassword(tsPassword);
235 var https = new HttpConfiguration();
236 https.addCustomizer(new SecureRequestCustomizer());
238 return new ServerConnector(jettyServer, sslContextFactory, new HttpConnectionFactory(https));
241 public ServerConnector httpConnector() {
242 return new ServerConnector(this.jettyServer);
246 public void setAafAuthentication(String filterPath) {
247 this.addFilterClass(filterPath, CadiFilter.class.getName());
251 public boolean isAaf() {
252 for (FilterHolder filter : context.getServletHandler().getFilters()) {
253 if (CadiFilter.class.getName().equals(filter.getClassName())) {
261 public void setBasicAuthentication(String user, String password, String servletPath) {
262 String srvltPath = servletPath;
264 if (user == null || user.isEmpty() || password == null || password.isEmpty()) {
265 throw new IllegalArgumentException("Missing user and/or password");
268 if (srvltPath == null || srvltPath.isEmpty()) {
272 final var hashLoginService = new HashLoginService();
273 final var userStore = new UserStore();
274 userStore.addUser(user, Credential.getCredential(password), new String[] {"user"});
275 hashLoginService.setUserStore(userStore);
276 hashLoginService.setName(this.connector.getName() + "-login-service");
278 var constraint = new Constraint();
279 constraint.setName(Constraint.__BASIC_AUTH);
280 constraint.setRoles(new String[] {"user"});
281 constraint.setAuthenticate(true);
283 var constraintMapping = new ConstraintMapping();
284 constraintMapping.setConstraint(constraint);
285 constraintMapping.setPathSpec(srvltPath);
287 var securityHandler = new ConstraintSecurityHandler();
288 securityHandler.setAuthenticator(new BasicAuthenticator());
289 securityHandler.setRealmName(this.connector.getName() + "-realm");
290 securityHandler.addConstraintMapping(constraintMapping);
291 securityHandler.setLoginService(hashLoginService);
293 this.context.setSecurityHandler(securityHandler);
296 this.password = password;
300 * jetty server execution.
305 logger.info("{}: STARTING", this);
307 this.jettyServer.start();
309 if (logger.isTraceEnabled()) {
310 logger.trace("{}: STARTED: {}", this, this.jettyServer.dump());
313 synchronized (this.startCondition) {
314 this.startCondition.notifyAll();
317 this.jettyServer.join();
319 } catch (InterruptedException e) {
320 logger.error("{}: error found while bringing up server", this, e);
321 Thread.currentThread().interrupt();
323 } catch (Exception e) {
324 logger.error("{}: error found while bringing up server", this, e);
329 public boolean waitedStart(long maxWaitTime) throws InterruptedException {
330 logger.info("{}: WAITED-START", this);
332 if (maxWaitTime < 0) {
333 throw new IllegalArgumentException("max-wait-time cannot be negative");
336 long pendingWaitTime = maxWaitTime;
342 synchronized (this.startCondition) {
344 while (!this.jettyServer.isRunning()) {
346 long startTs = System.currentTimeMillis();
348 this.startCondition.wait(pendingWaitTime);
350 if (maxWaitTime == 0) {
351 /* spurious notification */
355 long endTs = System.currentTimeMillis();
356 pendingWaitTime = pendingWaitTime - (endTs - startTs);
358 logger.info("{}: pending time is {} ms.", this, pendingWaitTime);
360 if (pendingWaitTime <= 0) {
364 } catch (InterruptedException e) {
365 logger.warn("{}: waited-start has been interrupted", this);
370 return this.jettyServer.isRunning();
375 public boolean start() {
376 logger.info("{}: STARTING", this);
378 synchronized (this) {
379 if (jettyThread == null || !this.jettyThread.isAlive()) {
381 this.jettyThread = new Thread(this);
382 this.jettyThread.setName(this.name + "-" + this.port);
383 this.jettyThread.start();
391 public boolean stop() {
392 logger.info("{}: STOPPING", this);
394 synchronized (this) {
395 if (jettyThread == null) {
399 if (!jettyThread.isAlive()) {
400 this.jettyThread = null;
404 this.connector.stop();
405 } catch (Exception e) {
406 logger.error("{}: error while stopping management server", this, e);
410 this.jettyServer.stop();
411 } catch (Exception e) {
412 logger.error("{}: error while stopping management server", this, e);
423 public void shutdown() {
424 logger.info("{}: SHUTTING DOWN", this);
428 Thread jettyThreadCopy;
429 synchronized (this) {
430 if ((jettyThreadCopy = this.jettyThread) == null) {
435 if (jettyThreadCopy.isAlive()) {
437 jettyThreadCopy.join(2000L);
438 } catch (InterruptedException e) {
439 logger.warn("{}: error while shutting down management server", this);
440 Thread.currentThread().interrupt();
442 if (!jettyThreadCopy.isInterrupted()) {
444 jettyThreadCopy.interrupt();
445 } catch (Exception e) {
447 logger.warn("{}: exception while shutting down (OK)", this, e);
452 this.jettyServer.destroy();
456 public boolean isAlive() {
457 if (this.jettyThread != null) {
458 return this.jettyThread.isAlive();
465 public int getPort() {
475 public String getName() {
484 public String getHost() {
493 public String getUser() {
500 * @return the password
503 public String getPassword() {
508 public void setSerializationProvider(String provider) {
509 throw new UnsupportedOperationException("setSerializationProvider()" + NOT_SUPPORTED);
513 public void addServletClass(String servletPath, String restClass) {
514 throw new UnsupportedOperationException("addServletClass()" + NOT_SUPPORTED);
518 public void addServletPackage(String servletPath, String restPackage) {
519 throw new UnsupportedOperationException("addServletPackage()" + NOT_SUPPORTED);
523 public void addServletResource(String servletPath, String resourceBase) {
524 throw new UnsupportedOperationException("addServletResource()" + NOT_SUPPORTED);