Merge "Add Unit Tests for GroupServlet"
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / SubscriptionServlet.java
1 /*******************************************************************************\r
2  * ============LICENSE_START==================================================\r
3  * * org.onap.dmaap\r
4  * * ===========================================================================\r
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
6  * * ===========================================================================\r
7  * * Licensed under the Apache License, Version 2.0 (the "License");\r
8  * * you may not use this file except in compliance with the License.\r
9  * * You may obtain a copy of the License at\r
10  * *\r
11  *  *      http://www.apache.org/licenses/LICENSE-2.0\r
12  * *\r
13  *  * Unless required by applicable law or agreed to in writing, software\r
14  * * distributed under the License is distributed on an "AS IS" BASIS,\r
15  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
16  * * See the License for the specific language governing permissions and\r
17  * * limitations under the License.\r
18  * * ============LICENSE_END====================================================\r
19  * *\r
20  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
21  * *\r
22  ******************************************************************************/\r
23 \r
24 \r
25 package org.onap.dmaap.datarouter.provisioning;\r
26 \r
27 import java.io.IOException;\r
28 import java.io.InvalidObjectException;\r
29 import java.net.HttpURLConnection;\r
30 import java.net.URL;\r
31 import java.util.List;\r
32 import java.util.Vector;\r
33 \r
34 import javax.servlet.http.HttpServletRequest;\r
35 import javax.servlet.http.HttpServletResponse;\r
36 \r
37 import org.json.JSONException;\r
38 import org.json.JSONObject;\r
39 import org.onap.dmaap.datarouter.authz.AuthorizationResponse;\r
40 import org.onap.dmaap.datarouter.provisioning.beans.EventLogRecord;\r
41 import org.onap.dmaap.datarouter.provisioning.beans.Subscription;\r
42 import org.onap.dmaap.datarouter.provisioning.eelf.EelfMsgs;\r
43 \r
44 import com.att.eelf.configuration.EELFLogger;\r
45 import com.att.eelf.configuration.EELFManager;\r
46 \r
47 /**\r
48  * This servlet handles provisioning for the <subscriptionURL> which is generated by the provisioning server to\r
49  * handle the inspection, modification, and deletion of a particular subscription to a feed. It supports DELETE to\r
50  * delete a subscription, GET to retrieve information about the subscription, and PUT to modify the subscription.  In DR\r
51  * 3.0, POST is also supported in order to reset the subscription timers for individual subscriptions.\r
52  *\r
53  * @author Robert Eby\r
54  * @version $Id$\r
55  */\r
56 @SuppressWarnings("serial")\r
57 public class SubscriptionServlet extends ProxyServlet {\r
58 \r
59     public static final String SUBCNTRL_CONTENT_TYPE = "application/vnd.att-dr.subscription-control";\r
60     //Adding EELF Logger Rally:US664892\r
61     private static EELFLogger eelflogger = EELFManager.getInstance()\r
62         .getLogger("org.onap.dmaap.datarouter.provisioning.SubscriptionServlet");\r
63 \r
64     /**\r
65      * DELETE on the &lt;subscriptionUrl&gt; -- delete a subscription. See the <i>Deleting a Subscription</i> section in\r
66      * the <b>Provisioning API</b> document for details on how this method should be invoked.\r
67      */\r
68     @Override\r
69     public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
70         setIpAndFqdnForEelf("doDelete");\r
71         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_SUBID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");\r
72         EventLogRecord elr = new EventLogRecord(req);\r
73         String message = isAuthorizedForProvisioning(req);\r
74         if (message != null) {\r
75             elr.setMessage(message);\r
76             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
77             eventlogger.info(elr);\r
78             resp.sendError(HttpServletResponse.SC_FORBIDDEN, message);\r
79             return;\r
80         }\r
81         if (isProxyServer()) {\r
82             super.doDelete(req, resp);\r
83             return;\r
84         }\r
85         String bhdr = req.getHeader(BEHALF_HEADER);\r
86         if (bhdr == null) {\r
87             message = "Missing " + BEHALF_HEADER + " header.";\r
88             elr.setMessage(message);\r
89             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
90             eventlogger.info(elr);\r
91             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
92             return;\r
93         }\r
94         int subid = getIdFromPath(req);\r
95         if (subid < 0) {\r
96             message = "Missing or bad subscription number.";\r
97             elr.setMessage(message);\r
98             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
99             eventlogger.info(elr);\r
100             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
101             return;\r
102         }\r
103         Subscription sub = Subscription.getSubscriptionById(subid);\r
104         if (sub == null) {\r
105             message = "Missing or bad subscription number.";\r
106             elr.setMessage(message);\r
107             elr.setResult(HttpServletResponse.SC_NOT_FOUND);\r
108             eventlogger.info(elr);\r
109             resp.sendError(HttpServletResponse.SC_NOT_FOUND, message);\r
110             return;\r
111         }\r
112         // Check with the Authorizer\r
113         AuthorizationResponse aresp = authz.decide(req);\r
114         if (!aresp.isAuthorized()) {\r
115             message = "Policy Engine disallows access.";\r
116             elr.setMessage(message);\r
117             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
118             eventlogger.info(elr);\r
119             resp.sendError(HttpServletResponse.SC_FORBIDDEN, message);\r
120             return;\r
121         }\r
122 \r
123         // Delete Subscription\r
124         if (doDelete(sub)) {\r
125             activeSubs--;\r
126             // send response\r
127             elr.setResult(HttpServletResponse.SC_NO_CONTENT);\r
128             eventlogger.info(elr);\r
129             resp.setStatus(HttpServletResponse.SC_NO_CONTENT);\r
130             provisioningDataChanged();\r
131         } else {\r
132             // Something went wrong with the DELETE\r
133             elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
134             eventlogger.info(elr);\r
135             resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);\r
136         }\r
137     }\r
138 \r
139     /**\r
140      * GET on the &lt;subscriptionUrl&gt; -- get information about a subscription. See the <i>Retreiving Information\r
141      * about a Subscription</i> section in the <b>Provisioning API</b> document for details on how this method should be\r
142      * invoked.\r
143      */\r
144     @Override\r
145     public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
146         setIpAndFqdnForEelf("doGet");\r
147         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_SUBID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");\r
148         EventLogRecord elr = new EventLogRecord(req);\r
149         String message = isAuthorizedForProvisioning(req);\r
150         if (message != null) {\r
151             elr.setMessage(message);\r
152             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
153             eventlogger.info(elr);\r
154             resp.sendError(HttpServletResponse.SC_FORBIDDEN, message);\r
155             return;\r
156         }\r
157         if (isProxyServer()) {\r
158             super.doGet(req, resp);\r
159             return;\r
160         }\r
161         String bhdr = req.getHeader(BEHALF_HEADER);\r
162         if (bhdr == null) {\r
163             message = "Missing " + BEHALF_HEADER + " header.";\r
164             elr.setMessage(message);\r
165             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
166             eventlogger.info(elr);\r
167             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
168             return;\r
169         }\r
170         int subid = getIdFromPath(req);\r
171         if (subid < 0) {\r
172             message = "Missing or bad subscription number.";\r
173             elr.setMessage(message);\r
174             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
175             eventlogger.info(elr);\r
176             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
177             return;\r
178         }\r
179         Subscription sub = Subscription.getSubscriptionById(subid);\r
180         if (sub == null) {\r
181             message = "Missing or bad subscription number.";\r
182             elr.setMessage(message);\r
183             elr.setResult(HttpServletResponse.SC_NOT_FOUND);\r
184             eventlogger.info(elr);\r
185             resp.sendError(HttpServletResponse.SC_NOT_FOUND, message);\r
186             return;\r
187         }\r
188         // Check with the Authorizer\r
189         AuthorizationResponse aresp = authz.decide(req);\r
190         if (!aresp.isAuthorized()) {\r
191             message = "Policy Engine disallows access.";\r
192             elr.setMessage(message);\r
193             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
194             eventlogger.info(elr);\r
195             resp.sendError(HttpServletResponse.SC_FORBIDDEN, message);\r
196             return;\r
197         }\r
198 \r
199         // send response\r
200         elr.setResult(HttpServletResponse.SC_OK);\r
201         eventlogger.info(elr);\r
202         resp.setStatus(HttpServletResponse.SC_OK);\r
203         resp.setContentType(SUBFULL_CONTENT_TYPE);\r
204         resp.getOutputStream().print(sub.asJSONObject(true).toString());\r
205     }\r
206 \r
207     /**\r
208      * PUT on the &lt;subscriptionUrl&gt; -- modify a subscription. See the <i>Modifying a Subscription</i> section in\r
209      * the <b>Provisioning API</b> document for details on how this method should be invoked.\r
210      */\r
211     @Override\r
212     public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
213         setIpAndFqdnForEelf("doPut");\r
214         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_SUBID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");\r
215         EventLogRecord elr = new EventLogRecord(req);\r
216         String message = isAuthorizedForProvisioning(req);\r
217         if (message != null) {\r
218             elr.setMessage(message);\r
219             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
220             eventlogger.info(elr);\r
221             resp.sendError(HttpServletResponse.SC_FORBIDDEN, message);\r
222             return;\r
223         }\r
224         if (isProxyServer()) {\r
225             super.doPut(req, resp);\r
226             return;\r
227         }\r
228         String bhdr = req.getHeader(BEHALF_HEADER);\r
229         if (bhdr == null) {\r
230             message = "Missing " + BEHALF_HEADER + " header.";\r
231             elr.setMessage(message);\r
232             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
233             eventlogger.info(elr);\r
234             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
235             return;\r
236         }\r
237         int subid = getIdFromPath(req);\r
238         if (subid < 0) {\r
239             message = "Missing or bad subscription number.";\r
240             elr.setMessage(message);\r
241             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
242             eventlogger.info(elr);\r
243             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
244             return;\r
245         }\r
246         Subscription oldsub = Subscription.getSubscriptionById(subid);\r
247         if (oldsub == null) {\r
248             message = "Missing or bad subscription number.";\r
249             elr.setMessage(message);\r
250             elr.setResult(HttpServletResponse.SC_NOT_FOUND);\r
251             eventlogger.info(elr);\r
252             resp.sendError(HttpServletResponse.SC_NOT_FOUND, message);\r
253             return;\r
254         }\r
255         // Check with the Authorizer\r
256         AuthorizationResponse aresp = authz.decide(req);\r
257         if (!aresp.isAuthorized()) {\r
258             message = "Policy Engine disallows access.";\r
259             elr.setMessage(message);\r
260             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
261             eventlogger.info(elr);\r
262             resp.sendError(HttpServletResponse.SC_FORBIDDEN, message);\r
263             return;\r
264         }\r
265         // check content type is SUB_CONTENT_TYPE, version 1.0\r
266         ContentHeader ch = getContentHeader(req);\r
267         String ver = ch.getAttribute("version");\r
268         if (!ch.getType().equals(SUB_BASECONTENT_TYPE) || !(ver.equals("1.0") || ver.equals("2.0"))) {\r
269             message = "Incorrect content-type";\r
270             elr.setMessage(message);\r
271             elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);\r
272             eventlogger.info(elr);\r
273             resp.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, message);\r
274             return;\r
275         }\r
276         JSONObject jo = getJSONfromInput(req);\r
277         if (jo == null) {\r
278             message = "Badly formed JSON";\r
279             elr.setMessage(message);\r
280             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
281             eventlogger.info(elr);\r
282             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
283             return;\r
284         }\r
285         if (intlogger.isDebugEnabled()) {\r
286             intlogger.debug(jo.toString());\r
287         }\r
288         Subscription sub = null;\r
289         try {\r
290             sub = new Subscription(jo);\r
291         } catch (InvalidObjectException e) {\r
292             message = e.getMessage();\r
293             elr.setMessage(message);\r
294             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
295             eventlogger.info(elr);\r
296             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
297             return;\r
298         }\r
299         sub.setSubid(oldsub.getSubid());\r
300         sub.setFeedid(oldsub.getFeedid());\r
301         sub.setSubscriber(bhdr);    // set from X-ATT-DR-ON-BEHALF-OF header\r
302 \r
303         String subjectgroup = (req.getHeader("X-ATT-DR-ON-BEHALF-OF-GROUP")); //Adding for group feature:Rally US708115\r
304         if (!oldsub.getSubscriber().equals(sub.getSubscriber()) && subjectgroup == null) {\r
305             message = "This subscriber must be modified by the same subscriber that created it.";\r
306             elr.setMessage(message);\r
307             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
308             eventlogger.info(elr);\r
309             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
310             return;\r
311         }\r
312 \r
313         // Update SUBSCRIPTIONS table entries\r
314         if (doUpdate(sub)) {\r
315             // send response\r
316             elr.setResult(HttpServletResponse.SC_OK);\r
317             eventlogger.info(elr);\r
318             resp.setStatus(HttpServletResponse.SC_OK);\r
319             resp.setContentType(SUBFULL_CONTENT_TYPE);\r
320             resp.getOutputStream().print(sub.asLimitedJSONObject().toString());\r
321 \r
322             /**Change Owner ship of Subscriber     Adding for group feature:Rally US708115*/\r
323             if (jo.has("changeowner") && subjectgroup != null) {\r
324                 Boolean changeowner = (Boolean) jo.get("changeowner");\r
325                 if (changeowner != null && changeowner.equals(true)) {\r
326                     sub.setSubscriber(req.getHeader(BEHALF_HEADER));\r
327                     sub.changeOwnerShip();\r
328                 }\r
329             }\r
330             /***End of change ownership*/\r
331 \r
332             provisioningDataChanged();\r
333         } else {\r
334             // Something went wrong with the UPDATE\r
335             elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
336             eventlogger.info(elr);\r
337             resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);\r
338         }\r
339     }\r
340 \r
341     /**\r
342      * POST on the &lt;subscriptionUrl&gt; -- control a subscription. See the <i>Resetting a Subscription's Retry\r
343      * Schedule</i> section in the <b>Provisioning API</b> document for details on how this method should be invoked.\r
344      */\r
345     @Override\r
346     public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {\r
347 // OLD pre-3.0 code\r
348 //        String message = "POST not allowed for the subscriptionURL.";\r
349 //        EventLogRecord elr = new EventLogRecord(req);\r
350 //        elr.setMessage(message);\r
351 //        elr.setResult(HttpServletResponse.SC_METHOD_NOT_ALLOWED);\r
352 //        eventlogger.info(elr);\r
353 //        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, message);\r
354 \r
355         setIpAndFqdnForEelf("doPost");\r
356         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF, req.getHeader(BEHALF_HEADER));\r
357         EventLogRecord elr = new EventLogRecord(req);\r
358         String message = isAuthorizedForProvisioning(req);\r
359         if (message != null) {\r
360             elr.setMessage(message);\r
361             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
362             eventlogger.info(elr);\r
363             resp.sendError(HttpServletResponse.SC_FORBIDDEN, message);\r
364             return;\r
365         }\r
366         if (isProxyServer()) {\r
367             super.doPost(req, resp);\r
368             return;\r
369         }\r
370         String bhdr = req.getHeader(BEHALF_HEADER);\r
371         if (bhdr == null) {\r
372             message = "Missing " + BEHALF_HEADER + " header.";\r
373             elr.setMessage(message);\r
374             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
375             eventlogger.info(elr);\r
376             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
377             return;\r
378         }\r
379         final int subid = getIdFromPath(req);\r
380         if (subid < 0 || Subscription.getSubscriptionById(subid) == null) {\r
381             message = "Missing or bad subscription number.";\r
382             elr.setMessage(message);\r
383             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
384             eventlogger.info(elr);\r
385             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
386             return;\r
387         }\r
388         // check content type is SUBCNTRL_CONTENT_TYPE, version 1.0\r
389         ContentHeader ch = getContentHeader(req);\r
390         String ver = ch.getAttribute("version");\r
391         if (!ch.getType().equals(SUBCNTRL_CONTENT_TYPE) || !ver.equals("1.0")) {\r
392             message = "Incorrect content-type";\r
393             elr.setMessage(message);\r
394             elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);\r
395             eventlogger.info(elr);\r
396             resp.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, message);\r
397             return;\r
398         }\r
399         // Check with the Authorizer\r
400         AuthorizationResponse aresp = authz.decide(req);\r
401         if (!aresp.isAuthorized()) {\r
402             message = "Policy Engine disallows access.";\r
403             elr.setMessage(message);\r
404             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
405             eventlogger.info(elr);\r
406             resp.sendError(HttpServletResponse.SC_FORBIDDEN, message);\r
407             return;\r
408         }\r
409         JSONObject jo = getJSONfromInput(req);\r
410         if (jo == null) {\r
411             message = "Badly formed JSON";\r
412             elr.setMessage(message);\r
413             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
414             eventlogger.info(elr);\r
415             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
416             return;\r
417         }\r
418         try {\r
419             // Only the active POD sends notifications\r
420             boolean active = SynchronizerTask.getSynchronizer().isActive();\r
421             boolean b = jo.getBoolean("failed");\r
422             if (active && !b) {\r
423                 // Notify all nodes to reset the subscription\r
424                 SubscriberNotifyThread t = new SubscriberNotifyThread();\r
425                 t.resetSubscription(subid);\r
426                 t.start();\r
427             }\r
428             // send response\r
429             elr.setResult(HttpServletResponse.SC_ACCEPTED);\r
430             eventlogger.info(elr);\r
431             resp.setStatus(HttpServletResponse.SC_ACCEPTED);\r
432         } catch (JSONException e) {\r
433             message = "Badly formed JSON";\r
434             elr.setMessage(message);\r
435             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
436             eventlogger.info(elr);\r
437             resp.sendError(HttpServletResponse.SC_BAD_REQUEST, message);\r
438         }\r
439     }\r
440 \r
441     /**\r
442      * A Thread class used to serially send reset notifications to all nodes in the DR network, when a POST is received\r
443      * for a subscription.\r
444      */\r
445     public class SubscriberNotifyThread extends Thread {\r
446 \r
447         public static final String URL_TEMPLATE = "http://%s/internal/resetSubscription/%d";\r
448         private List<String> urls = new Vector<String>();\r
449 \r
450         public SubscriberNotifyThread() {\r
451             setName("SubscriberNotifyThread");\r
452         }\r
453 \r
454         public void resetSubscription(int subid) {\r
455             for (String nodename : BaseServlet.getNodes()) {\r
456                 String u = String.format(URL_TEMPLATE, nodename, subid);\r
457                 urls.add(u);\r
458             }\r
459         }\r
460 \r
461         public void run() {\r
462             try {\r
463                 while (!urls.isEmpty()) {\r
464                     String u = urls.remove(0);\r
465                     try {\r
466                         URL url = new URL(u);\r
467                         HttpURLConnection conn = (HttpURLConnection) url.openConnection();\r
468                         conn.connect();\r
469                         conn.getContentLength();    // Force the GET through\r
470                         conn.disconnect();\r
471                     } catch (IOException e) {\r
472                         intlogger.info("IOException Error accessing URL: " + u + ": " + e.getMessage());\r
473                     }\r
474                 }\r
475             } catch (Exception e) {\r
476                 intlogger.warn("Caught exception in SubscriberNotifyThread: " + e);\r
477                 e.printStackTrace();\r
478             }\r
479         }\r
480     }\r
481 }\r