2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.policy.common.endpoints.http.server.internal;
23 import com.fasterxml.jackson.annotation.JsonIgnore;
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.authentication.BasicAuthenticator;
31 import org.eclipse.jetty.server.HttpConfiguration;
32 import org.eclipse.jetty.server.HttpConnectionFactory;
33 import org.eclipse.jetty.server.SecureRequestCustomizer;
34 import org.eclipse.jetty.server.Server;
35 import org.eclipse.jetty.server.ServerConnector;
36 import org.eclipse.jetty.server.Slf4jRequestLog;
37 import org.eclipse.jetty.servlet.ServletContextHandler;
38 import org.eclipse.jetty.util.security.Constraint;
39 import org.eclipse.jetty.util.security.Credential;
40 import org.eclipse.jetty.util.ssl.SslContextFactory;
41 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * Http Server implementation using Embedded Jetty
48 public abstract class JettyServletServer implements HttpServletServer, Runnable {
51 * Keystore/Truststore system property names
53 public static final String SYSTEM_KEYSTORE_PROPERTY_NAME = "javax.net.ssl.keyStore";
54 public static final String SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME = "javax.net.ssl.keyStorePassword";
55 public static final String SYSTEM_TRUSTSTORE_PROPERTY_NAME = "javax.net.ssl.trustStore";
56 public static final String SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME = "javax.net.ssl.trustStorePassword";
61 private static Logger logger = LoggerFactory.getLogger(JettyServletServer.class);
66 protected final String name;
71 protected final String host;
76 protected final int port;
79 * server auth user name
81 protected String user;
84 * server auth password name
86 protected String password;
89 * server base context path
91 protected final String contextPath;
94 * embedded jetty server
96 protected final Server jettyServer;
101 protected final ServletContextHandler context;
106 protected final ServerConnector connector;
111 protected volatile Thread jettyThread;
116 protected Object startCondition = new Object();
121 * @param name server name
122 * @param host server host
123 * @param port server port
124 * @param contextPath context path
126 * @throws IllegalArgumentException if invalid parameters are passed in
128 public JettyServletServer(String name, boolean https, String host, int port, String contextPath) {
129 String srvName = name;
130 String srvHost = host;
131 String ctxtPath = contextPath;
133 if (srvName == null || srvName.isEmpty()) {
134 srvName = "http-" + port;
137 if (port <= 0 || port >= 65535) {
138 throw new IllegalArgumentException("Invalid Port provided: " + port);
141 if (srvHost == null || srvHost.isEmpty()) {
142 srvHost = "localhost";
145 if (ctxtPath == null || ctxtPath.isEmpty()) {
154 this.contextPath = ctxtPath;
156 this.context = new ServletContextHandler(ServletContextHandler.SESSIONS);
157 this.context.setContextPath(ctxtPath);
159 this.jettyServer = new Server();
160 this.jettyServer.setRequestLog(new Slf4jRequestLog());
163 this.connector = httpsConnector();
165 this.connector = httpConnector();
167 this.connector.setName(srvName);
168 this.connector.setReuseAddress(true);
169 this.connector.setPort(port);
170 this.connector.setHost(srvHost);
172 this.jettyServer.addConnector(this.connector);
173 this.jettyServer.setHandler(context);
176 public JettyServletServer(String name, String host, int port, String contextPath) {
177 this(name, false, host, port, contextPath);
181 public void addFilterClass(String aFilterPath, String aFilterClass) {
182 if (aFilterClass == null || aFilterClass.isEmpty()) {
183 throw new IllegalArgumentException("No filter class provided");
186 String filterPath = aFilterPath;
187 if (aFilterPath == null || aFilterPath.isEmpty()) {
191 context.addFilter(aFilterClass, filterPath,
192 EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
195 public ServerConnector httpsConnector() {
196 SslContextFactory sslContextFactory = new SslContextFactory();
198 String keyStore = System.getProperty(SYSTEM_KEYSTORE_PROPERTY_NAME);
199 if (keyStore != null) {
200 sslContextFactory.setKeyStorePath(keyStore);
202 String ksPassword = System.getProperty(SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME);
203 if (ksPassword != null)
204 sslContextFactory.setKeyStorePassword(ksPassword);
207 String trustStore = System.getProperty(SYSTEM_TRUSTSTORE_PROPERTY_NAME);
208 if (trustStore != null) {
209 sslContextFactory.setTrustStorePath(trustStore);
211 String tsPassword = System.getProperty(SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME);
212 if (tsPassword != null)
213 sslContextFactory.setTrustStorePassword(tsPassword);
216 HttpConfiguration https = new HttpConfiguration();
217 https.addCustomizer(new SecureRequestCustomizer());
219 return new ServerConnector(jettyServer, sslContextFactory, new HttpConnectionFactory(https));
222 public ServerConnector httpConnector() {
223 return new ServerConnector(this.jettyServer);
227 public void setBasicAuthentication(String user, String password, String servletPath) {
228 String srvltPath = servletPath;
230 if (user == null || user.isEmpty() || password == null || password.isEmpty()) {
231 throw new IllegalArgumentException("Missing user and/or password");
234 if (srvltPath == null || srvltPath.isEmpty()) {
238 HashLoginService hashLoginService = new HashLoginService();
239 hashLoginService.putUser(user, Credential.getCredential(password), new String[] {"user"});
240 hashLoginService.setName(this.connector.getName() + "-login-service");
242 Constraint constraint = new Constraint();
243 constraint.setName(Constraint.__BASIC_AUTH);
244 constraint.setRoles(new String[] {"user"});
245 constraint.setAuthenticate(true);
247 ConstraintMapping constraintMapping = new ConstraintMapping();
248 constraintMapping.setConstraint(constraint);
249 constraintMapping.setPathSpec(srvltPath);
251 ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
252 securityHandler.setAuthenticator(new BasicAuthenticator());
253 securityHandler.setRealmName(this.connector.getName() + "-realm");
254 securityHandler.addConstraintMapping(constraintMapping);
255 securityHandler.setLoginService(hashLoginService);
257 this.context.setSecurityHandler(securityHandler);
260 this.password = password;
264 * jetty server execution
269 logger.info("{}: STARTING", this);
271 this.jettyServer.start();
273 if (logger.isInfoEnabled()) {
274 logger.info("{}: STARTED: {}", this, this.jettyServer.dump());
277 synchronized (this.startCondition) {
278 this.startCondition.notifyAll();
281 this.jettyServer.join();
282 } catch (Exception e) {
283 logger.error("{}: error found while bringing up server", this, e);
288 public boolean waitedStart(long maxWaitTime) throws InterruptedException {
289 logger.info("{}: WAITED-START", this);
291 if (maxWaitTime < 0) {
292 throw new IllegalArgumentException("max-wait-time cannot be negative");
295 long pendingWaitTime = maxWaitTime;
301 synchronized (this.startCondition) {
303 while (!this.jettyServer.isRunning()) {
305 long startTs = System.currentTimeMillis();
307 this.startCondition.wait(pendingWaitTime);
309 if (maxWaitTime == 0) {
310 /* spurious notification */
314 long endTs = System.currentTimeMillis();
315 pendingWaitTime = pendingWaitTime - (endTs - startTs);
317 logger.info("{}: pending time is {} ms.", this, pendingWaitTime);
319 if (pendingWaitTime <= 0) {
323 } catch (InterruptedException e) {
324 logger.warn("{}: waited-start has been interrupted", this);
329 return this.jettyServer.isRunning();
334 public boolean start() {
335 logger.info("{}: STARTING", this);
337 synchronized (this) {
338 if (jettyThread == null || !this.jettyThread.isAlive()) {
340 this.jettyThread = new Thread(this);
341 this.jettyThread.setName(this.name + "-" + this.port);
342 this.jettyThread.start();
350 public boolean stop() {
351 logger.info("{}: STOPPING", this);
353 synchronized (this) {
354 if (jettyThread == null) {
358 if (!jettyThread.isAlive()) {
359 this.jettyThread = null;
363 this.connector.stop();
364 } catch (Exception e) {
365 logger.error("{}: error while stopping management server", this, e);
369 this.jettyServer.stop();
370 } catch (Exception e) {
371 logger.error("{}: error while stopping management server", this, e);
382 public void shutdown() {
383 logger.info("{}: SHUTTING DOWN", this);
387 if (this.jettyThread == null) {
391 Thread jettyThreadCopy = this.jettyThread;
393 if (jettyThreadCopy.isAlive()) {
395 jettyThreadCopy.join(2000L);
396 } catch (InterruptedException e) {
397 logger.warn("{}: error while shutting down management server", this);
398 Thread.currentThread().interrupt();
400 if (!jettyThreadCopy.isInterrupted()) {
402 jettyThreadCopy.interrupt();
403 } catch (Exception e) {
405 logger.warn("{}: exception while shutting down (OK)", this, e);
410 this.jettyServer.destroy();
414 public boolean isAlive() {
415 if (this.jettyThread != null) {
416 return this.jettyThread.isAlive();
423 public int getPort() {
430 public String getName() {
437 public String getHost() {
444 public String getUser() {
449 * @return the password
452 public String getPassword() {
457 public String toString() {
458 StringBuilder builder = new StringBuilder();
459 builder.append("JettyServer [name=").append(name).append(", host=").append(host).append(", port=").append(port)
460 .append(", user=").append(user).append(", password=").append(password != null).append(", contextPath=")
461 .append(contextPath).append(", jettyServer=").append(jettyServer).append(", context=")
462 .append(this.context).append(", connector=").append(connector).append(", jettyThread=")
463 .append(jettyThread).append("]");
464 return builder.toString();