Refactor Prov DB handling
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / LogServlet.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 static org.onap.dmaap.datarouter.provisioning.utils.HttpServletUtils.sendResponseError;\r
28 \r
29 import com.att.eelf.configuration.EELFLogger;\r
30 import com.att.eelf.configuration.EELFManager;\r
31 import java.io.IOException;\r
32 import java.sql.Connection;\r
33 import java.sql.PreparedStatement;\r
34 import java.sql.ResultSet;\r
35 import java.sql.SQLException;\r
36 import java.text.ParseException;\r
37 import java.text.SimpleDateFormat;\r
38 import java.util.Date;\r
39 import java.util.HashMap;\r
40 import java.util.Map;\r
41 import javax.servlet.ServletOutputStream;\r
42 import javax.servlet.http.HttpServletRequest;\r
43 import javax.servlet.http.HttpServletResponse;\r
44 import org.onap.dmaap.datarouter.provisioning.beans.DeliveryRecord;\r
45 import org.onap.dmaap.datarouter.provisioning.beans.EventLogRecord;\r
46 import org.onap.dmaap.datarouter.provisioning.beans.ExpiryRecord;\r
47 import org.onap.dmaap.datarouter.provisioning.beans.LOGJSONable;\r
48 import org.onap.dmaap.datarouter.provisioning.beans.PublishRecord;\r
49 import org.onap.dmaap.datarouter.provisioning.beans.Subscription;\r
50 import org.onap.dmaap.datarouter.provisioning.eelf.EelfMsgs;\r
51 import org.onap.dmaap.datarouter.provisioning.utils.LOGJSONObject;\r
52 import org.onap.dmaap.datarouter.provisioning.utils.ProvDbUtils;\r
53 \r
54 \r
55 /**\r
56  * This servlet handles requests to the <feedLogURL> and  <subLogURL>,\r
57  * which are generated by the provisioning server to handle the log query API.\r
58  *\r
59  * @author Robert Eby\r
60  * @version $Id: LogServlet.java,v 1.11 2014/03/28 17:27:02 eby Exp $\r
61  */\r
62 @SuppressWarnings("serial")\r
63 \r
64 public class LogServlet extends BaseServlet {\r
65     //Adding EELF Logger Rally:US664892\r
66     private static EELFLogger eelfLogger = EELFManager.getInstance().getLogger(LogServlet.class);\r
67     private static final long TWENTYFOUR_HOURS = (24 * 60 * 60 * 1000L);\r
68     private static final String FMT_1 = "yyyy-MM-dd'T'HH:mm:ss'Z'";\r
69     private static final String FMT_2 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";\r
70     private static final String PUBLISHSQL = "publishSQL";\r
71     private static final String STATUSSQL = "statusSQL";\r
72     private static final String RESULTSQL = "resultSQL";\r
73     private static final String FILENAMESQL = "filenameSQL";\r
74     private static final String TIMESQL = "timeSQL";\r
75     private static final String LOG_RECORDSSQL = "select * from LOG_RECORDS where FEEDID = ";\r
76 \r
77     private final boolean isfeedlog;\r
78 \r
79     public abstract static class RowHandler {\r
80         private final ServletOutputStream out;\r
81         private final String[] fields;\r
82         private boolean firstrow;\r
83 \r
84         /**\r
85          * Row setter.\r
86          * @param out ServletOutputStream\r
87          * @param fieldparam String field\r
88          * @param bool boolean\r
89          */\r
90         RowHandler(ServletOutputStream out, String fieldparam, boolean bool) {\r
91             this.out = out;\r
92             this.firstrow = bool;\r
93             this.fields = (fieldparam != null) ? fieldparam.split(":") : null;\r
94         }\r
95 \r
96         /**\r
97          * Handling row from DB.\r
98          * @param rs DB Resultset\r
99          */\r
100         void handleRow(ResultSet rs) {\r
101             try {\r
102                 LOGJSONable js = buildJSONable(rs);\r
103                 LOGJSONObject jo = js.asJSONObject();\r
104                 if (fields != null) {\r
105                     // filter out unwanted fields\r
106                     LOGJSONObject j2 = new LOGJSONObject();\r
107                     for (String key : fields) {\r
108                         Object val = jo.opt(key);\r
109                         if (val != null) {\r
110                             j2.put(key, val);\r
111                         }\r
112                     }\r
113                     jo = j2;\r
114                 }\r
115                 String str = firstrow ? "\n" : ",\n";\r
116                 str += jo.toString();\r
117                 out.print(str);\r
118                 firstrow = false;\r
119             } catch (Exception exception) {\r
120                 intlogger.info("Failed to handle row. Exception = " + exception.getMessage(),exception);\r
121             }\r
122         }\r
123 \r
124         public abstract LOGJSONable buildJSONable(ResultSet rs) throws SQLException;\r
125     }\r
126 \r
127     public static class PublishRecordRowHandler extends RowHandler {\r
128         PublishRecordRowHandler(ServletOutputStream out, String fields, boolean bool) {\r
129             super(out, fields, bool);\r
130         }\r
131 \r
132         @Override\r
133         public LOGJSONable buildJSONable(ResultSet rs) throws SQLException {\r
134             return new PublishRecord(rs);\r
135         }\r
136     }\r
137 \r
138     public static class DeliveryRecordRowHandler extends RowHandler {\r
139         DeliveryRecordRowHandler(ServletOutputStream out, String fields, boolean bool) {\r
140             super(out, fields, bool);\r
141         }\r
142 \r
143         @Override\r
144         public LOGJSONable buildJSONable(ResultSet rs) throws SQLException {\r
145             return new DeliveryRecord(rs);\r
146         }\r
147     }\r
148 \r
149     public static class ExpiryRecordRowHandler extends RowHandler {\r
150         ExpiryRecordRowHandler(ServletOutputStream out, String fields, boolean bool) {\r
151             super(out, fields, bool);\r
152         }\r
153 \r
154         @Override\r
155         public LOGJSONable buildJSONable(ResultSet rs) throws SQLException {\r
156             return new ExpiryRecord(rs);\r
157         }\r
158     }\r
159 \r
160     /**\r
161      * This class must be created from either a {@link FeedLogServlet} or a {@link SubLogServlet}.\r
162      * @param isFeedLog boolean to handle those places where a feedlog request is different from a sublog request\r
163      */\r
164     LogServlet(boolean isFeedLog) {\r
165         this.isfeedlog = isFeedLog;\r
166     }\r
167 \r
168     /**\r
169      * DELETE a logging URL -- not supported.\r
170      */\r
171     @Override\r
172     public void doDelete(HttpServletRequest req, HttpServletResponse resp) {\r
173         setIpFqdnRequestIDandInvocationIDForEelf("doDelete", req);\r
174         eelfLogger.info(EelfMsgs.ENTRY);\r
175         try {\r
176             eelfLogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID,\r
177                 req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");\r
178             String message = "DELETE not allowed for the logURL.";\r
179             EventLogRecord elr = new EventLogRecord(req);\r
180             elr.setMessage(message);\r
181             elr.setResult(HttpServletResponse.SC_METHOD_NOT_ALLOWED);\r
182             eventlogger.error(elr.toString());\r
183             sendResponseError(resp, HttpServletResponse.SC_METHOD_NOT_ALLOWED, message, eventlogger);\r
184         } finally {\r
185             eelfLogger.info(EelfMsgs.EXIT);\r
186         }\r
187     }\r
188 \r
189     /**\r
190      * GET a logging URL -- retrieve logging data for a feed or subscription.\r
191      * See the <b>Logging API</b> document for details on how this method should be invoked.\r
192      */\r
193     @Override\r
194     public void doGet(HttpServletRequest req, HttpServletResponse resp) {\r
195         setIpFqdnRequestIDandInvocationIDForEelf("doGet", req);\r
196         eelfLogger.info(EelfMsgs.ENTRY);\r
197         try {\r
198             eelfLogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID,\r
199                 req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");\r
200             int id = getIdFromPath(req);\r
201             if (id < 0) {\r
202                 sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST,\r
203                     "Missing or bad feed/subscription number.", eventlogger);\r
204                 return;\r
205             }\r
206             Map<String, String> map = buildMapFromRequest(req);\r
207             if (map.get("err") != null) {\r
208                 sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST,\r
209                     "Invalid arguments: " + map.get("err"), eventlogger);\r
210                 return;\r
211             }\r
212             // check Accept: header??\r
213             resp.setStatus(HttpServletResponse.SC_OK);\r
214             resp.setContentType(LOGLIST_CONTENT_TYPE);\r
215             try (ServletOutputStream out = resp.getOutputStream()) {\r
216                 final String fields = req.getParameter("fields");\r
217                 out.print("[");\r
218                 if (isfeedlog) {\r
219                     // Handle /feedlog/feedid request\r
220                     boolean firstrow = true;\r
221                     // 1. Collect publish records for this feed\r
222                     RowHandler rh = new PublishRecordRowHandler(out, fields, firstrow);\r
223                     getPublishRecordsForFeed(id, rh, map);\r
224                     firstrow = rh.firstrow;\r
225                     // 2. Collect delivery records for subscriptions to this feed\r
226                     rh = new DeliveryRecordRowHandler(out, fields, firstrow);\r
227                     getDeliveryRecordsForFeed(id, rh, map);\r
228                     firstrow = rh.firstrow;\r
229                     // 3. Collect expiry records for subscriptions to this feed\r
230                     rh = new ExpiryRecordRowHandler(out, fields, firstrow);\r
231                     getExpiryRecordsForFeed(id, rh, map);\r
232                 } else {\r
233                     // Handle /sublog/subid request\r
234                     Subscription sub = Subscription.getSubscriptionById(id);\r
235                     if (sub != null) {\r
236                         // 1. Collect publish records for the feed this subscription feeds\r
237                         RowHandler rh = new PublishRecordRowHandler(out, fields, true);\r
238                         getPublishRecordsForFeed(sub.getFeedid(), rh, map);\r
239                         // 2. Collect delivery records for this subscription\r
240                         rh = new DeliveryRecordRowHandler(out, fields, rh.firstrow);\r
241                         getDeliveryRecordsForSubscription(id, rh, map);\r
242                         // 3. Collect expiry records for this subscription\r
243                         rh = new ExpiryRecordRowHandler(out, fields, rh.firstrow);\r
244                         getExpiryRecordsForSubscription(id, rh, map);\r
245                     }\r
246                 }\r
247                 out.print("]");\r
248             } catch (IOException ioe) {\r
249                 eventlogger.error("PROV0141 LogServlet.doGet: " + ioe.getMessage(), ioe);\r
250             }\r
251         } finally {\r
252             eelfLogger.info(EelfMsgs.EXIT);\r
253         }\r
254     }\r
255 \r
256     /**\r
257      * PUT a logging URL -- not supported.\r
258      */\r
259     @Override\r
260     public void doPut(HttpServletRequest req, HttpServletResponse resp) {\r
261         setIpFqdnRequestIDandInvocationIDForEelf("doPut", req);\r
262         eelfLogger.info(EelfMsgs.ENTRY);\r
263         try {\r
264             eelfLogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID,\r
265                 req.getHeader(BEHALF_HEADER),getIdFromPath(req) + "");\r
266             String message = "PUT not allowed for the logURL.";\r
267             EventLogRecord elr = new EventLogRecord(req);\r
268             elr.setMessage(message);\r
269             elr.setResult(HttpServletResponse.SC_METHOD_NOT_ALLOWED);\r
270             eventlogger.error(elr.toString());\r
271             sendResponseError(resp, HttpServletResponse.SC_METHOD_NOT_ALLOWED, message, eventlogger);\r
272         } finally {\r
273             eelfLogger.info(EelfMsgs.EXIT);\r
274         }\r
275     }\r
276 \r
277     /**\r
278      * POST a logging URL -- not supported.\r
279      */\r
280     @Override\r
281     public void doPost(HttpServletRequest req, HttpServletResponse resp) {\r
282         setIpFqdnRequestIDandInvocationIDForEelf("doPost", req);\r
283         eelfLogger.info(EelfMsgs.ENTRY);\r
284         try {\r
285             eelfLogger.info(EelfMsgs.MESSAGE_WITH_BEHALF, req.getHeader(BEHALF_HEADER));\r
286             String message = "POST not allowed for the logURL.";\r
287             EventLogRecord elr = new EventLogRecord(req);\r
288             elr.setMessage(message);\r
289             elr.setResult(HttpServletResponse.SC_METHOD_NOT_ALLOWED);\r
290             eventlogger.error(elr.toString());\r
291             sendResponseError(resp, HttpServletResponse.SC_METHOD_NOT_ALLOWED, message, eventlogger);\r
292         } finally {\r
293             eelfLogger.info(EelfMsgs.EXIT);\r
294         }\r
295     }\r
296 \r
297     private Map<String, String> buildMapFromRequest(HttpServletRequest req) {\r
298         Map<String, String> map = new HashMap<>();\r
299         String str = req.getParameter("type");\r
300         if (str != null) {\r
301             if ("pub".equals(str) || "del".equals(str) || "exp".equals(str)) {\r
302                 map.put("type", str);\r
303             } else {\r
304                 map.put("err", "bad type");\r
305                 return map;\r
306             }\r
307         } else {\r
308             map.put("type", "all");\r
309         }\r
310         map.put(PUBLISHSQL, "");\r
311         map.put(STATUSSQL, "");\r
312         map.put(RESULTSQL, "");\r
313         map.put(REASON_SQL, "");\r
314         map.put(FILENAMESQL, "");\r
315 \r
316         str = req.getParameter("publishId");\r
317         if (str != null) {\r
318             if (str.indexOf("'") >= 0) {\r
319                 map.put("err", "bad publishId");\r
320                 return map;\r
321             }\r
322             map.put(PUBLISHSQL, " AND PUBLISH_ID = '" + str + "'");\r
323         }\r
324 \r
325         str = req.getParameter("filename");\r
326         if (str != null) {\r
327             map.put(FILENAMESQL, " AND FILENAME = '" + str + "'");\r
328         }\r
329 \r
330         str = req.getParameter("statusCode");\r
331         if (str != null) {\r
332             String sql = null;\r
333             switch (str) {\r
334                 case "success":\r
335                     sql = " AND STATUS >= 200 AND STATUS < 300";\r
336                     break;\r
337                 case "redirect":\r
338                     sql = " AND STATUS >= 300 AND STATUS < 400";\r
339                     break;\r
340                 case "failure":\r
341                     sql = " AND STATUS >= 400";\r
342                     break;\r
343                 default:\r
344                     try {\r
345                         int statusCode = Integer.parseInt(str);\r
346                         if ((statusCode >= 100 && statusCode < 600) || (statusCode == -1)) {\r
347                             sql = " AND STATUS = " + statusCode;\r
348                         }\r
349                     } catch (NumberFormatException e) {\r
350                         intlogger.error("Failed to parse input", e);\r
351                     }\r
352                     break;\r
353             }\r
354             if (sql == null) {\r
355                 map.put("err", "bad statusCode");\r
356                 return map;\r
357             }\r
358             map.put(STATUSSQL, sql);\r
359             map.put(RESULTSQL, sql.replaceAll("STATUS", "RESULT"));\r
360         }\r
361 \r
362         str = req.getParameter("expiryReason");\r
363         if (str != null) {\r
364             map.put("type", "exp");\r
365             switch (str) {\r
366                 case "notRetryable":\r
367                     map.put(REASON_SQL, " AND REASON = 'notRetryable'");\r
368                     break;\r
369                 case "retriesExhausted":\r
370                     map.put(REASON_SQL, " AND REASON = 'retriesExhausted'");\r
371                     break;\r
372                 case "diskFull":\r
373                     map.put(REASON_SQL, " AND REASON = 'diskFull'");\r
374                     break;\r
375                 case "other":\r
376                     map.put(REASON_SQL, " AND REASON = 'other'");\r
377                     break;\r
378                 default:\r
379                     map.put("err", "bad expiryReason");\r
380                     return map;\r
381             }\r
382         }\r
383 \r
384         long stime = getTimeFromParam(req.getParameter("start"));\r
385         if (stime < 0) {\r
386             map.put("err", "bad start");\r
387             return map;\r
388         }\r
389         long etime = getTimeFromParam(req.getParameter("end"));\r
390         if (etime < 0) {\r
391             map.put("err", "bad end");\r
392             return map;\r
393         }\r
394         if (stime == 0 && etime == 0) {\r
395             etime = System.currentTimeMillis();\r
396             stime = etime - TWENTYFOUR_HOURS;\r
397         } else if (stime == 0) {\r
398             stime = etime - TWENTYFOUR_HOURS;\r
399         } else if (etime == 0) {\r
400             etime = stime + TWENTYFOUR_HOURS;\r
401         }\r
402         map.put(TIMESQL, String.format(" AND EVENT_TIME >= %d AND EVENT_TIME <= %d", stime, etime));\r
403         return map;\r
404     }\r
405 \r
406     private long getTimeFromParam(final String str) {\r
407         if (str == null) {\r
408             return 0;\r
409         }\r
410         try {\r
411             // First, look for an RFC 3339 date\r
412             String fmt = (str.indexOf('.') > 0) ? FMT_2 : FMT_1;\r
413             SimpleDateFormat sdf = new SimpleDateFormat(fmt);\r
414             Date date = sdf.parse(str);\r
415             return date.getTime();\r
416         } catch (ParseException parseException) {\r
417             intlogger.error("Exception in getting Time :- " + parseException.getMessage(),parseException);\r
418         }\r
419         try {\r
420             // Also allow a long (in ms); useful for testing\r
421             return Long.parseLong(str);\r
422         } catch (NumberFormatException numberFormatException) {\r
423             intlogger.error("Exception in getting Time :- " + numberFormatException.getMessage(),numberFormatException);\r
424         }\r
425         intlogger.info("Error parsing time=" + str);\r
426         return -1;\r
427     }\r
428 \r
429     private void getPublishRecordsForFeed(int feedid, RowHandler rh, Map<String, String> map) {\r
430         String type = map.get("type");\r
431         if ("all".equals(type) || "pub".equals(type)) {\r
432             String sql = LOG_RECORDSSQL + feedid\r
433                 + " AND TYPE = 'pub'"\r
434                 + map.get(TIMESQL) + map.get(PUBLISHSQL) + map.get(STATUSSQL) + map.get(FILENAMESQL);\r
435             getRecordsForSQL(sql, rh);\r
436         }\r
437     }\r
438 \r
439     private void getDeliveryRecordsForFeed(int feedid, RowHandler rh, Map<String, String> map) {\r
440         String type = map.get("type");\r
441         if ("all".equals(type) || "del".equals(type)) {\r
442             String sql = LOG_RECORDSSQL + feedid\r
443                 + " AND TYPE = 'del'"\r
444                 + map.get(TIMESQL) + map.get(PUBLISHSQL) + map.get(RESULTSQL);\r
445             getRecordsForSQL(sql, rh);\r
446         }\r
447     }\r
448 \r
449     private void getDeliveryRecordsForSubscription(int subid, RowHandler rh, Map<String, String> map) {\r
450         String type = map.get("type");\r
451         if ("all".equals(type) || "del".equals(type)) {\r
452             String sql = "select * from LOG_RECORDS where DELIVERY_SUBID = " + subid\r
453                 + " AND TYPE = 'del'"\r
454                 + map.get(TIMESQL) + map.get(PUBLISHSQL) + map.get(RESULTSQL);\r
455             getRecordsForSQL(sql, rh);\r
456         }\r
457     }\r
458 \r
459     private void getExpiryRecordsForFeed(int feedid, RowHandler rh, Map<String, String> map) {\r
460         String type = map.get("type");\r
461         if ("all".equals(type) || "exp".equals(type)) {\r
462             String st = map.get(STATUSSQL);\r
463             if (st == null || st.length() == 0) {\r
464                 String sql = LOG_RECORDSSQL + feedid\r
465                     + " AND TYPE = 'exp'"\r
466                     + map.get(TIMESQL) + map.get(PUBLISHSQL) + map.get(REASON_SQL);\r
467                 getRecordsForSQL(sql, rh);\r
468             }\r
469         }\r
470     }\r
471 \r
472     private void getExpiryRecordsForSubscription(int subid, RowHandler rh, Map<String, String> map) {\r
473         String type = map.get("type");\r
474         if ("all".equals(type) || "exp".equals(type)) {\r
475             String st = map.get(STATUSSQL);\r
476             if (st == null || st.length() == 0) {\r
477                 String sql = "select * from LOG_RECORDS where DELIVERY_SUBID = " + subid\r
478                     + " AND TYPE = 'exp'"\r
479                     + map.get(TIMESQL) + map.get(PUBLISHSQL) + map.get(REASON_SQL);\r
480                 getRecordsForSQL(sql, rh);\r
481             }\r
482         }\r
483     }\r
484 \r
485     private void getRecordsForSQL(String sql, RowHandler rh) {\r
486         intlogger.debug(sql);\r
487         long start = System.currentTimeMillis();\r
488         try (Connection conn = ProvDbUtils.getInstance().getConnection();\r
489             PreparedStatement ps = conn.prepareStatement(sql);\r
490             ResultSet rs = ps.executeQuery()) {\r
491             while (rs.next()) {\r
492                 rh.handleRow(rs);\r
493             }\r
494         } catch (SQLException sqlException) {\r
495             intlogger.info("Failed to get Records. Exception = " + sqlException.getMessage(),sqlException);\r
496         }\r
497         intlogger.debug("Time: " + (System.currentTimeMillis() - start) + " ms");\r
498     }\r
499 }\r