2 * ============LICENSE_START=======================================================
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
22 package org.onap.policy.common.endpoints.http.server.internal;
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;
50 * Http Server implementation using Embedded Jetty.
52 public abstract class JettyServletServer implements HttpServletServer, Runnable {
55 * Keystore/Truststore system property names.
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
65 private static Logger logger = LoggerFactory.getLogger(JettyServletServer.class);
67 private static final String NOT_SUPPORTED = " is not supported on this type of jetty server";
72 protected final String name;
75 * Server host address.
77 protected final String host;
80 * Server port to bind.
82 protected final int port;
85 * Server auth user name.
87 protected String user;
90 * Server auth password name.
92 protected String password;
95 * Server base context path.
97 protected final String contextPath;
100 * Embedded jetty server.
102 protected final Server jettyServer;
107 protected final ServletContextHandler context;
112 protected final ServerConnector connector;
117 protected Thread jettyThread;
122 protected Object startCondition = new Object();
127 * @param name server name
128 * @param host server host
129 * @param port server port
130 * @param contextPath context path
132 * @throws IllegalArgumentException if invalid parameters are passed in
134 public JettyServletServer(String name, boolean https, String host, int port, String contextPath) {
135 String srvName = name;
137 if (srvName == null || srvName.isEmpty()) {
138 srvName = "http-" + port;
141 if (port <= 0 || port >= 65535) {
142 throw new IllegalArgumentException("Invalid Port provided: " + port);
145 String srvHost = host;
146 if (srvHost == null || srvHost.isEmpty()) {
147 srvHost = "localhost";
150 String ctxtPath = contextPath;
151 if (ctxtPath == null || ctxtPath.isEmpty()) {
160 this.contextPath = ctxtPath;
162 this.context = new ServletContextHandler(ServletContextHandler.SESSIONS);
163 this.context.setContextPath(ctxtPath);
165 this.jettyServer = new Server();
167 CustomRequestLog requestLog =
168 new CustomRequestLog(new Slf4jRequestLogWriter(), CustomRequestLog.EXTENDED_NCSA_FORMAT);
169 this.jettyServer.setRequestLog(requestLog);
172 this.connector = httpsConnector();
174 this.connector = httpConnector();
177 this.connector.setName(srvName);
178 this.connector.setReuseAddress(true);
179 this.connector.setPort(port);
180 this.connector.setHost(srvHost);
182 this.jettyServer.addConnector(this.connector);
183 this.jettyServer.setHandler(context);
186 public JettyServletServer(String name, String host, int port, String contextPath) {
187 this(name, false, host, port, contextPath);
191 public void addFilterClass(String filterPath, String filterClass) {
192 if (filterClass == null || filterClass.isEmpty()) {
193 throw new IllegalArgumentException("No filter class provided");
196 String tempFilterPath = filterPath;
197 if (filterPath == null || filterPath.isEmpty()) {
198 tempFilterPath = "/*";
201 context.addFilter(filterClass, tempFilterPath, EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
205 * Returns the https connector.
207 * @return the server connector
209 public ServerConnector httpsConnector() {
210 SslContextFactory sslContextFactory = new SslContextFactory.Server();
212 String keyStore = System.getProperty(SYSTEM_KEYSTORE_PROPERTY_NAME);
213 if (keyStore != null) {
214 sslContextFactory.setKeyStorePath(keyStore);
216 String ksPassword = System.getProperty(SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME);
217 if (ksPassword != null) {
218 sslContextFactory.setKeyStorePassword(ksPassword);
222 String trustStore = System.getProperty(SYSTEM_TRUSTSTORE_PROPERTY_NAME);
223 if (trustStore != null) {
224 sslContextFactory.setTrustStorePath(trustStore);
226 String tsPassword = System.getProperty(SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME);
227 if (tsPassword != null) {
228 sslContextFactory.setTrustStorePassword(tsPassword);
232 HttpConfiguration https = new HttpConfiguration();
233 https.addCustomizer(new SecureRequestCustomizer());
235 return new ServerConnector(jettyServer, sslContextFactory, new HttpConnectionFactory(https));
238 public ServerConnector httpConnector() {
239 return new ServerConnector(this.jettyServer);
243 public void setAafAuthentication(String filterPath) {
244 this.addFilterClass(filterPath, CadiFilter.class.getName());
248 public boolean isAaf() {
249 for (FilterHolder filter : context.getServletHandler().getFilters()) {
250 if (CadiFilter.class.getName().equals(filter.getClassName())) {
258 public void setBasicAuthentication(String user, String password, String servletPath) {
259 String srvltPath = servletPath;
261 if (user == null || user.isEmpty() || password == null || password.isEmpty()) {
262 throw new IllegalArgumentException("Missing user and/or password");
265 if (srvltPath == null || srvltPath.isEmpty()) {
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");
275 Constraint constraint = new Constraint();
276 constraint.setName(Constraint.__BASIC_AUTH);
277 constraint.setRoles(new String[] {"user"});
278 constraint.setAuthenticate(true);
280 ConstraintMapping constraintMapping = new ConstraintMapping();
281 constraintMapping.setConstraint(constraint);
282 constraintMapping.setPathSpec(srvltPath);
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);
290 this.context.setSecurityHandler(securityHandler);
293 this.password = password;
297 * jetty server execution.
302 logger.info("{}: STARTING", this);
304 this.jettyServer.start();
306 if (logger.isTraceEnabled()) {
307 logger.trace("{}: STARTED: {}", this, this.jettyServer.dump());
310 synchronized (this.startCondition) {
311 this.startCondition.notifyAll();
314 this.jettyServer.join();
315 } catch (Exception e) {
316 logger.error("{}: error found while bringing up server", this, e);
321 public boolean waitedStart(long maxWaitTime) throws InterruptedException {
322 logger.info("{}: WAITED-START", this);
324 if (maxWaitTime < 0) {
325 throw new IllegalArgumentException("max-wait-time cannot be negative");
328 long pendingWaitTime = maxWaitTime;
334 synchronized (this.startCondition) {
336 while (!this.jettyServer.isRunning()) {
338 long startTs = System.currentTimeMillis();
340 this.startCondition.wait(pendingWaitTime);
342 if (maxWaitTime == 0) {
343 /* spurious notification */
347 long endTs = System.currentTimeMillis();
348 pendingWaitTime = pendingWaitTime - (endTs - startTs);
350 logger.info("{}: pending time is {} ms.", this, pendingWaitTime);
352 if (pendingWaitTime <= 0) {
356 } catch (InterruptedException e) {
357 logger.warn("{}: waited-start has been interrupted", this);
362 return this.jettyServer.isRunning();
367 public boolean start() {
368 logger.info("{}: STARTING", this);
370 synchronized (this) {
371 if (jettyThread == null || !this.jettyThread.isAlive()) {
373 this.jettyThread = new Thread(this);
374 this.jettyThread.setName(this.name + "-" + this.port);
375 this.jettyThread.start();
383 public boolean stop() {
384 logger.info("{}: STOPPING", this);
386 synchronized (this) {
387 if (jettyThread == null) {
391 if (!jettyThread.isAlive()) {
392 this.jettyThread = null;
396 this.connector.stop();
397 } catch (Exception e) {
398 logger.error("{}: error while stopping management server", this, e);
402 this.jettyServer.stop();
403 } catch (Exception e) {
404 logger.error("{}: error while stopping management server", this, e);
415 public void shutdown() {
416 logger.info("{}: SHUTTING DOWN", this);
420 Thread jettyThreadCopy;
421 synchronized (this) {
422 if ((jettyThreadCopy = this.jettyThread) == null) {
427 if (jettyThreadCopy.isAlive()) {
429 jettyThreadCopy.join(2000L);
430 } catch (InterruptedException e) {
431 logger.warn("{}: error while shutting down management server", this);
432 Thread.currentThread().interrupt();
434 if (!jettyThreadCopy.isInterrupted()) {
436 jettyThreadCopy.interrupt();
437 } catch (Exception e) {
439 logger.warn("{}: exception while shutting down (OK)", this, e);
444 this.jettyServer.destroy();
448 public boolean isAlive() {
449 if (this.jettyThread != null) {
450 return this.jettyThread.isAlive();
457 public int getPort() {
467 public String getName() {
476 public String getHost() {
485 public String getUser() {
492 * @return the password
495 public String getPassword() {
500 public void setSerializationProvider(String provider) {
501 throw new UnsupportedOperationException("setSerializationProvider()" + NOT_SUPPORTED);
505 public void addServletClass(String servletPath, String restClass) {
506 throw new UnsupportedOperationException("addServletClass()" + NOT_SUPPORTED);
510 public void addServletPackage(String servletPath, String restPackage) {
511 throw new UnsupportedOperationException("addServletPackage()" + NOT_SUPPORTED);
515 public void addServletResource(String servletPath, String resourceBase) {
516 throw new UnsupportedOperationException("addServletResource()" + NOT_SUPPORTED);
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();