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;
27 import javax.servlet.DispatcherType;
29 import org.eclipse.jetty.security.ConstraintMapping;
30 import org.eclipse.jetty.security.ConstraintSecurityHandler;
31 import org.eclipse.jetty.security.HashLoginService;
32 import org.eclipse.jetty.security.UserStore;
33 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
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.Slf4jRequestLog;
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.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * Http Server implementation using Embedded Jetty.
53 public abstract class JettyServletServer implements HttpServletServer, Runnable {
56 * Keystore/Truststore system property names.
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";
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";
66 private static Logger logger = LoggerFactory.getLogger(JettyServletServer.class);
71 protected final String name;
74 * Server host address.
76 protected final String host;
79 * Server port to bind.
81 protected final int port;
84 * Server auth user name.
86 protected String user;
89 * Server auth password name.
91 protected String password;
94 * Server base context path.
96 protected final String contextPath;
99 * Embedded jetty server.
101 protected final Server jettyServer;
106 protected final ServletContextHandler context;
111 protected final ServerConnector connector;
116 protected volatile Thread jettyThread;
121 protected Object startCondition = new Object();
126 * @param name server name
127 * @param host server host
128 * @param port server port
129 * @param contextPath context path
131 * @throws IllegalArgumentException if invalid parameters are passed in
133 public JettyServletServer(String name, boolean https, String host, int port, String contextPath) {
134 String srvName = name;
136 if (srvName == null || srvName.isEmpty()) {
137 srvName = "http-" + port;
140 if (port <= 0 || port >= 65535) {
141 throw new IllegalArgumentException("Invalid Port provided: " + port);
144 String srvHost = host;
145 if (srvHost == null || srvHost.isEmpty()) {
146 srvHost = "localhost";
149 String ctxtPath = contextPath;
150 if (ctxtPath == null || ctxtPath.isEmpty()) {
159 this.contextPath = ctxtPath;
161 this.context = new ServletContextHandler(ServletContextHandler.SESSIONS);
162 this.context.setContextPath(ctxtPath);
164 this.jettyServer = new Server();
165 this.jettyServer.setRequestLog(new Slf4jRequestLog());
168 this.connector = httpsConnector();
170 this.connector = httpConnector();
173 this.connector.setName(srvName);
174 this.connector.setReuseAddress(true);
175 this.connector.setPort(port);
176 this.connector.setHost(srvHost);
178 this.jettyServer.addConnector(this.connector);
179 this.jettyServer.setHandler(context);
182 public JettyServletServer(String name, String host, int port, String contextPath) {
183 this(name, false, host, port, contextPath);
187 public void addFilterClass(String filterPath, String filterClass) {
188 if (filterClass == null || filterClass.isEmpty()) {
189 throw new IllegalArgumentException("No filter class provided");
192 String tempFilterPath = filterPath;
193 if (filterPath == null || filterPath.isEmpty()) {
194 tempFilterPath = "/*";
197 context.addFilter(filterClass, tempFilterPath, EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
201 * Returns the https connector.
203 * @return the server connector
205 public ServerConnector httpsConnector() {
206 SslContextFactory sslContextFactory = new SslContextFactory();
208 String keyStore = System.getProperty(SYSTEM_KEYSTORE_PROPERTY_NAME);
209 if (keyStore != null) {
210 sslContextFactory.setKeyStorePath(keyStore);
212 String ksPassword = System.getProperty(SYSTEM_KEYSTORE_PASSWORD_PROPERTY_NAME);
213 if (ksPassword != null) {
214 sslContextFactory.setKeyStorePassword(ksPassword);
218 String trustStore = System.getProperty(SYSTEM_TRUSTSTORE_PROPERTY_NAME);
219 if (trustStore != null) {
220 sslContextFactory.setTrustStorePath(trustStore);
222 String tsPassword = System.getProperty(SYSTEM_TRUSTSTORE_PASSWORD_PROPERTY_NAME);
223 if (tsPassword != null) {
224 sslContextFactory.setTrustStorePassword(tsPassword);
228 HttpConfiguration https = new HttpConfiguration();
229 https.addCustomizer(new SecureRequestCustomizer());
231 return new ServerConnector(jettyServer, sslContextFactory, new HttpConnectionFactory(https));
234 public ServerConnector httpConnector() {
235 return new ServerConnector(this.jettyServer);
239 public void setAafAuthentication(String filterPath) {
240 this.addFilterClass(filterPath, CadiFilter.class.getCanonicalName());
244 public boolean isAaf() {
245 for (FilterHolder filter : context.getServletHandler().getFilters()) {
246 if (CadiFilter.class.getCanonicalName().equals(filter.getClassName())) {
254 public void setBasicAuthentication(String user, String password, String servletPath) {
255 String srvltPath = servletPath;
257 if (user == null || user.isEmpty() || password == null || password.isEmpty()) {
258 throw new IllegalArgumentException("Missing user and/or password");
261 if (srvltPath == null || srvltPath.isEmpty()) {
265 final HashLoginService hashLoginService = new HashLoginService();
266 final UserStore userStore = new UserStore();
267 userStore.addUser(user, Credential.getCredential(password), new String[] {"user"});
268 hashLoginService.setUserStore(userStore);
269 hashLoginService.setName(this.connector.getName() + "-login-service");
271 Constraint constraint = new Constraint();
272 constraint.setName(Constraint.__BASIC_AUTH);
273 constraint.setRoles(new String[] {"user"});
274 constraint.setAuthenticate(true);
276 ConstraintMapping constraintMapping = new ConstraintMapping();
277 constraintMapping.setConstraint(constraint);
278 constraintMapping.setPathSpec(srvltPath);
280 ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
281 securityHandler.setAuthenticator(new BasicAuthenticator());
282 securityHandler.setRealmName(this.connector.getName() + "-realm");
283 securityHandler.addConstraintMapping(constraintMapping);
284 securityHandler.setLoginService(hashLoginService);
286 this.context.setSecurityHandler(securityHandler);
289 this.password = password;
293 * jetty server execution.
298 logger.info("{}: STARTING", this);
300 this.jettyServer.start();
302 if (logger.isInfoEnabled()) {
303 logger.info("{}: STARTED: {}", this, this.jettyServer.dump());
306 synchronized (this.startCondition) {
307 this.startCondition.notifyAll();
310 this.jettyServer.join();
311 } catch (Exception e) {
312 logger.error("{}: error found while bringing up server", this, e);
317 public boolean waitedStart(long maxWaitTime) throws InterruptedException {
318 logger.info("{}: WAITED-START", this);
320 if (maxWaitTime < 0) {
321 throw new IllegalArgumentException("max-wait-time cannot be negative");
324 long pendingWaitTime = maxWaitTime;
330 synchronized (this.startCondition) {
332 while (!this.jettyServer.isRunning()) {
334 long startTs = System.currentTimeMillis();
336 this.startCondition.wait(pendingWaitTime);
338 if (maxWaitTime == 0) {
339 /* spurious notification */
343 long endTs = System.currentTimeMillis();
344 pendingWaitTime = pendingWaitTime - (endTs - startTs);
346 logger.info("{}: pending time is {} ms.", this, pendingWaitTime);
348 if (pendingWaitTime <= 0) {
352 } catch (InterruptedException e) {
353 logger.warn("{}: waited-start has been interrupted", this);
358 return this.jettyServer.isRunning();
363 public boolean start() {
364 logger.info("{}: STARTING", this);
366 synchronized (this) {
367 if (jettyThread == null || !this.jettyThread.isAlive()) {
369 this.jettyThread = new Thread(this);
370 this.jettyThread.setName(this.name + "-" + this.port);
371 this.jettyThread.start();
379 public boolean stop() {
380 logger.info("{}: STOPPING", this);
382 synchronized (this) {
383 if (jettyThread == null) {
387 if (!jettyThread.isAlive()) {
388 this.jettyThread = null;
392 this.connector.stop();
393 } catch (Exception e) {
394 logger.error("{}: error while stopping management server", this, e);
398 this.jettyServer.stop();
399 } catch (Exception e) {
400 logger.error("{}: error while stopping management server", this, e);
411 public void shutdown() {
412 logger.info("{}: SHUTTING DOWN", this);
416 if (this.jettyThread == null) {
420 Thread jettyThreadCopy = this.jettyThread;
422 if (jettyThreadCopy.isAlive()) {
424 jettyThreadCopy.join(2000L);
425 } catch (InterruptedException e) {
426 logger.warn("{}: error while shutting down management server", this);
427 Thread.currentThread().interrupt();
429 if (!jettyThreadCopy.isInterrupted()) {
431 jettyThreadCopy.interrupt();
432 } catch (Exception e) {
434 logger.warn("{}: exception while shutting down (OK)", this, e);
439 this.jettyServer.destroy();
443 public boolean isAlive() {
444 if (this.jettyThread != null) {
445 return this.jettyThread.isAlive();
452 public int getPort() {
461 public String getName() {
470 public String getHost() {
479 public String getUser() {
486 * @return the password
489 public String getPassword() {
494 public String toString() {
495 StringBuilder builder = new StringBuilder();
496 builder.append("JettyServer [name=").append(name).append(", host=").append(host).append(", port=").append(port)
497 .append(", user=").append(user).append(", password=").append(password != null).append(", contextPath=")
498 .append(contextPath).append(", jettyServer=").append(jettyServer).append(", context=")
499 .append(this.context).append(", connector=").append(connector).append(", jettyThread=")
500 .append(jettyThread).append("]");
501 return builder.toString();