97166ec704125a9db0f995fc0aa5e88bb297602b
[policy/common.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2017 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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 package org.onap.policy.common.endpoints.http.server.internal;
22
23 import com.fasterxml.jackson.annotation.JsonIgnore;
24
25 import org.eclipse.jetty.security.ConstraintMapping;
26 import org.eclipse.jetty.security.ConstraintSecurityHandler;
27 import org.eclipse.jetty.security.HashLoginService;
28 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
29 import org.eclipse.jetty.server.Server;
30 import org.eclipse.jetty.server.ServerConnector;
31 import org.eclipse.jetty.server.Slf4jRequestLog;
32 import org.eclipse.jetty.servlet.ServletContextHandler;
33 import org.eclipse.jetty.util.security.Constraint;
34 import org.eclipse.jetty.util.security.Credential;
35 import org.onap.policy.common.endpoints.http.server.HttpServletServer;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * Http Server implementation using Embedded Jetty
41  */
42 public abstract class JettyServletServer implements HttpServletServer, Runnable {
43
44     /**
45      * Logger
46      */
47     private static Logger logger = LoggerFactory.getLogger(JettyServletServer.class);
48
49     /**
50      * server name
51      */
52     protected final String name;
53
54     /**
55      * server host address
56      */
57     protected final String host;
58
59     /**
60      * server port to bind
61      */
62     protected final int port;
63
64     /**
65      * server auth user name
66      */
67     protected String user;
68
69     /**
70      * server auth password name
71      */
72     protected String password;
73
74     /**
75      * server base context path
76      */
77     protected final String contextPath;
78
79     /**
80      * embedded jetty server
81      */
82     protected final Server jettyServer;
83
84     /**
85      * servlet context
86      */
87     protected final ServletContextHandler context;
88
89     /**
90      * jetty connector
91      */
92     protected final ServerConnector connector;
93
94     /**
95      * jetty thread
96      */
97     protected volatile Thread jettyThread;
98
99     /**
100      * start condition
101      */
102     protected Object startCondition = new Object();
103
104     /**
105      * constructor
106      * 
107      * @param name server name
108      * @param host server host
109      * @param port server port
110      * @param contextPath context path
111      * 
112      * @throws IllegalArgumentException if invalid parameters are passed in
113      */
114     public JettyServletServer(String name, String host, int port, String contextPath) {
115         String srvName = name;
116         String srvHost = host;
117         String ctxtPath = contextPath;
118
119         if (srvName == null || srvName.isEmpty()) {
120             srvName = "http-" + port;
121         }
122
123         if (port <= 0 && port >= 65535) {
124             throw new IllegalArgumentException("Invalid Port provided: " + port);
125         }
126
127         if (srvHost == null || srvHost.isEmpty()) {
128             srvHost = "localhost";
129         }
130
131         if (ctxtPath == null || ctxtPath.isEmpty()) {
132             ctxtPath = "/";
133         }
134
135         this.name = srvName;
136
137         this.host = srvHost;
138         this.port = port;
139
140         this.contextPath = ctxtPath;
141
142         this.context = new ServletContextHandler(ServletContextHandler.SESSIONS);
143         this.context.setContextPath(ctxtPath);
144
145         this.jettyServer = new Server();
146         this.jettyServer.setRequestLog(new Slf4jRequestLog());
147
148         this.connector = new ServerConnector(this.jettyServer);
149         this.connector.setName(srvName);
150         this.connector.setReuseAddress(true);
151         this.connector.setPort(port);
152         this.connector.setHost(srvHost);
153
154         this.jettyServer.addConnector(this.connector);
155         this.jettyServer.setHandler(context);
156     }
157
158     @Override
159     public void setBasicAuthentication(String user, String password, String servletPath) {
160         String srvltPath = servletPath;
161
162         if (user == null || user.isEmpty() || password == null || password.isEmpty()) {
163             throw new IllegalArgumentException("Missing user and/or password");
164         }
165
166         if (srvltPath == null || srvltPath.isEmpty()) {
167             srvltPath = "/*";
168         }
169
170         HashLoginService hashLoginService = new HashLoginService();
171         hashLoginService.putUser(user, Credential.getCredential(password), new String[] {"user"});
172         hashLoginService.setName(this.connector.getName() + "-login-service");
173
174         Constraint constraint = new Constraint();
175         constraint.setName(Constraint.__BASIC_AUTH);
176         constraint.setRoles(new String[] {"user"});
177         constraint.setAuthenticate(true);
178
179         ConstraintMapping constraintMapping = new ConstraintMapping();
180         constraintMapping.setConstraint(constraint);
181         constraintMapping.setPathSpec(srvltPath);
182
183         ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
184         securityHandler.setAuthenticator(new BasicAuthenticator());
185         securityHandler.setRealmName(this.connector.getName() + "-realm");
186         securityHandler.addConstraintMapping(constraintMapping);
187         securityHandler.setLoginService(hashLoginService);
188
189         this.context.setSecurityHandler(securityHandler);
190
191         this.user = user;
192         this.password = password;
193     }
194
195     /**
196      * jetty server execution
197      */
198     @Override
199     public void run() {
200         try {
201             logger.info("{}: STARTING", this);
202
203             this.jettyServer.start();
204
205             if (logger.isInfoEnabled()) {
206                 logger.info("{}: STARTED: {}", this, this.jettyServer.dump());
207             }
208
209             synchronized (this.startCondition) {
210                 this.startCondition.notifyAll();
211             }
212
213             this.jettyServer.join();
214         } catch (Exception e) {
215             logger.error("{}: error found while bringing up server", this, e);
216         }
217     }
218
219     @Override
220     public boolean waitedStart(long maxWaitTime) throws InterruptedException {
221         logger.info("{}: WAITED-START", this);
222
223         if (maxWaitTime < 0) {
224             throw new IllegalArgumentException("max-wait-time cannot be negative");
225         }
226
227         long pendingWaitTime = maxWaitTime;
228
229         if (!this.start()) {
230             return false;
231         }
232
233         synchronized (this.startCondition) {
234
235             while (!this.jettyServer.isRunning()) {
236                 try {
237                     long startTs = System.currentTimeMillis();
238
239                     this.startCondition.wait(pendingWaitTime);
240
241                     if (maxWaitTime == 0) {
242                         /* spurious notification */
243                         continue;
244                     }
245
246                     long endTs = System.currentTimeMillis();
247                     pendingWaitTime = pendingWaitTime - (endTs - startTs);
248
249                     logger.info("{}: pending time is {} ms.", this, pendingWaitTime);
250
251                     if (pendingWaitTime <= 0) {
252                         return false;
253                     }
254
255                 } catch (InterruptedException e) {
256                     logger.warn("{}: waited-start has been interrupted", this);
257                     throw e;
258                 }
259             }
260
261             return this.jettyServer.isRunning();
262         }
263     }
264
265     @Override
266     public boolean start() {
267         logger.info("{}: STARTING", this);
268
269         synchronized (this) {
270             if (jettyThread == null || !this.jettyThread.isAlive()) {
271
272                 this.jettyThread = new Thread(this);
273                 this.jettyThread.setName(this.name + "-" + this.port);
274                 this.jettyThread.start();
275             }
276         }
277
278         return true;
279     }
280
281     @Override
282     public boolean stop() {
283         logger.info("{}: STOPPING", this);
284
285         synchronized (this) {
286             if (jettyThread == null) {
287                 return true;
288             }
289
290             if (!jettyThread.isAlive()) {
291                 this.jettyThread = null;
292             }
293
294             try {
295                 this.connector.stop();
296             } catch (Exception e) {
297                 logger.error("{}: error while stopping management server", this, e);
298             }
299
300             try {
301                 this.jettyServer.stop();
302             } catch (Exception e) {
303                 logger.error("{}: error while stopping management server", this, e);
304                 return false;
305             }
306
307             Thread.yield();
308         }
309
310         return true;
311     }
312
313     @Override
314     public void shutdown() {
315         logger.info("{}: SHUTTING DOWN", this);
316
317         this.stop();
318
319         if (this.jettyThread == null) {
320             return;
321         }
322
323         Thread jettyThreadCopy = this.jettyThread;
324
325         if (jettyThreadCopy.isAlive()) {
326             try {
327                 jettyThreadCopy.join(2000L);
328             } catch (InterruptedException e) {
329                 logger.warn("{}: error while shutting down management server", this);
330                 Thread.currentThread().interrupt();
331             }
332             if (!jettyThreadCopy.isInterrupted()) {
333                 try {
334                     jettyThreadCopy.interrupt();
335                 } catch (Exception e) {
336                     // do nothing
337                     logger.warn("{}: exception while shutting down (OK)", this, e);
338                 }
339             }
340         }
341
342         this.jettyServer.destroy();
343     }
344
345     @Override
346     public boolean isAlive() {
347         if (this.jettyThread != null) {
348             return this.jettyThread.isAlive();
349         }
350
351         return false;
352     }
353
354     @Override
355     public int getPort() {
356         return this.port;
357     }
358
359     /**
360      * @return the name
361      */
362     public String getName() {
363         return name;
364     }
365
366     /**
367      * @return the host
368      */
369     public String getHost() {
370         return host;
371     }
372
373     /**
374      * @return the user
375      */
376     public String getUser() {
377         return user;
378     }
379
380     /**
381      * @return the password
382      */
383     @JsonIgnore
384     public String getPassword() {
385         return password;
386     }
387
388     @Override
389     public String toString() {
390         StringBuilder builder = new StringBuilder();
391         builder.append("JettyServer [name=").append(name).append(", host=").append(host).append(", port=").append(port)
392                 .append(", user=").append(user).append(", password=").append(password != null).append(", contextPath=")
393                 .append(contextPath).append(", jettyServer=").append(jettyServer).append(", context=")
394                 .append(this.context).append(", connector=").append(connector).append(", jettyThread=")
395                 .append(jettyThread).append("]");
396         return builder.toString();
397     }
398
399 }