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