Merge "Fix formatting"
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / beans / IngressRoute.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.beans;\r
26 \r
27 import java.net.InetAddress;\r
28 import java.net.UnknownHostException;\r
29 import java.sql.Connection;\r
30 import java.sql.PreparedStatement;\r
31 import java.sql.ResultSet;\r
32 import java.sql.SQLException;\r
33 import java.sql.Statement;\r
34 import java.util.ArrayList;\r
35 import java.util.Collection;\r
36 import java.util.Set;\r
37 import java.util.SortedSet;\r
38 import java.util.TreeSet;\r
39 \r
40 import javax.servlet.http.HttpServletRequest;\r
41 \r
42 import org.apache.commons.codec.binary.Base64;\r
43 import org.apache.log4j.Logger;\r
44 import org.json.JSONArray;\r
45 import org.json.JSONObject;\r
46 import org.onap.dmaap.datarouter.provisioning.utils.DB;\r
47 \r
48 /**\r
49  * The representation of one route in the Ingress Route Table.\r
50  *\r
51  * @author Robert P. Eby\r
52  * @version $Id: IngressRoute.java,v 1.3 2013/12/16 20:30:23 eby Exp $\r
53  */\r
54 public class IngressRoute extends NodeClass implements Comparable<IngressRoute> {\r
55 \r
56     private static Logger intlogger = Logger.getLogger("org.onap.dmaap.datarouter.provisioning.internal");\r
57     private final int seq;\r
58     private final int feedid;\r
59     private final String userid;\r
60     private final String subnet;\r
61     private int nodelist;\r
62     private SortedSet<String> nodes;\r
63 \r
64     /**\r
65      * Get all IngressRoutes in the database, sorted in order according to their sequence field.\r
66      *\r
67      * @return a sorted set of IngressRoutes\r
68      */\r
69     public static SortedSet<IngressRoute> getAllIngressRoutes() {\r
70         return getAllIngressRoutesForSQL("select SEQUENCE, FEEDID, USERID, SUBNET, NODESET from INGRESS_ROUTES");\r
71     }\r
72 \r
73     /**\r
74      * Get all IngressRoutes in the database with a particular sequence number.\r
75      *\r
76      * @param seq the sequence number\r
77      * @return a set of IngressRoutes\r
78      */\r
79     public static Set<IngressRoute> getIngressRoutesForSeq(int seq) {\r
80         return getAllIngressRoutesForSQL(\r
81                 "select SEQUENCE, FEEDID, USERID, SUBNET, NODESET from INGRESS_ROUTES where SEQUENCE = " + seq);\r
82     }\r
83 \r
84     private static SortedSet<IngressRoute> getAllIngressRoutesForSQL(String sql) {\r
85         SortedSet<IngressRoute> set = new TreeSet<IngressRoute>();\r
86         try {\r
87             DB db = new DB();\r
88             @SuppressWarnings("resource")\r
89             Connection conn = db.getConnection();\r
90             try (Statement stmt = conn.createStatement()) {\r
91                 try (ResultSet rs = stmt.executeQuery(sql)) {\r
92                     while (rs.next()) {\r
93                         int seq = rs.getInt("SEQUENCE");\r
94                         int feedid = rs.getInt("FEEDID");\r
95                         String user = rs.getString("USERID");\r
96                         String subnet = rs.getString("SUBNET");\r
97                         int nodeset = rs.getInt("NODESET");\r
98                         set.add(new IngressRoute(seq, feedid, user, subnet, nodeset));\r
99                     }\r
100                 }\r
101             }\r
102             db.release(conn);\r
103         } catch (SQLException e) {\r
104             intlogger.error("SQLException " + e.getMessage());\r
105         }\r
106         return set;\r
107     }\r
108 \r
109     /**\r
110      * Get the maximum node set ID in use in the DB.\r
111      *\r
112      * @return the integer value of the maximum\r
113      */\r
114     public static int getMaxNodeSetID() {\r
115         return getMax("select max(SETID) as MAX from NODESETS");\r
116     }\r
117 \r
118     /**\r
119      * Get the maximum node sequence number in use in the DB.\r
120      *\r
121      * @return the integer value of the maximum\r
122      */\r
123     public static int getMaxSequence() {\r
124         return getMax("select max(SEQUENCE) as MAX from INGRESS_ROUTES");\r
125     }\r
126 \r
127     private static int getMax(String sql) {\r
128         int rv = 0;\r
129         try {\r
130             DB db = new DB();\r
131             @SuppressWarnings("resource")\r
132             Connection conn = db.getConnection();\r
133             try (Statement stmt = conn.createStatement()) {\r
134                 try (ResultSet rs = stmt.executeQuery(sql)) {\r
135                     if (rs.next()) {\r
136                         rv = rs.getInt("MAX");\r
137                     }\r
138                 }\r
139             }\r
140             db.release(conn);\r
141         } catch (SQLException e) {\r
142             intlogger.error("SQLException " + e.getMessage());\r
143         }\r
144         return rv;\r
145     }\r
146 \r
147     /**\r
148      * Get an Ingress Route for a particular feed ID, user, and subnet\r
149      *\r
150      * @param feedid the Feed ID to look for\r
151      * @param user the user name to look for\r
152      * @param subnet the subnet to look for\r
153      * @return the Ingress Route, or null of there is none\r
154      */\r
155     public static IngressRoute getIngressRoute(int feedid, String user, String subnet) {\r
156         IngressRoute v = null;\r
157         PreparedStatement ps = null;\r
158         try {\r
159             DB db = new DB();\r
160             @SuppressWarnings("resource")\r
161             Connection conn = db.getConnection();\r
162             String sql = "select SEQUENCE, NODESET from INGRESS_ROUTES where FEEDID = ? AND USERID = ? and SUBNET = ?";\r
163             ps = conn.prepareStatement(sql);\r
164             ps.setInt(1, feedid);\r
165             ps.setString(2, user);\r
166             ps.setString(3, subnet);\r
167             try (ResultSet rs = ps.executeQuery()) {\r
168                 if (rs.next()) {\r
169                     int seq = rs.getInt("SEQUENCE");\r
170                     int nodeset = rs.getInt("NODESET");\r
171                     v = new IngressRoute(seq, feedid, user, subnet, nodeset);\r
172                 }\r
173             }\r
174             ps.close();\r
175             db.release(conn);\r
176         } catch (SQLException e) {\r
177             intlogger.error("SQLException " + e.getMessage());\r
178         } finally {\r
179             try {\r
180                 if (ps != null) {\r
181                     ps.close();\r
182                 }\r
183             } catch (SQLException e) {\r
184                 intlogger.error("SQLException " + e.getMessage());\r
185             }\r
186         }\r
187         return v;\r
188     }\r
189 \r
190     /**\r
191      * Get a collection of all Ingress Routes with a particular sequence number.\r
192      *\r
193      * @param seq the sequence number to look for\r
194      * @return the collection (may be empty).\r
195      */\r
196     public static Collection<IngressRoute> getIngressRoute(int seq) {\r
197         Collection<IngressRoute> rv = new ArrayList<IngressRoute>();\r
198         try {\r
199             DB db = new DB();\r
200             @SuppressWarnings("resource")\r
201             Connection conn = db.getConnection();\r
202             String sql = "select FEEDID, USERID, SUBNET, NODESET from INGRESS_ROUTES where SEQUENCE = ?";\r
203             try (PreparedStatement ps = conn.prepareStatement(sql)) {\r
204                 ps.setInt(1, seq);\r
205                 try (ResultSet rs = ps.executeQuery()) {\r
206                     while (rs.next()) {\r
207                         int feedid = rs.getInt("FEEDID");\r
208                         String user = rs.getString("USERID");\r
209                         String subnet = rs.getString("SUBNET");\r
210                         int nodeset = rs.getInt("NODESET");\r
211                         rv.add(new IngressRoute(seq, feedid, user, subnet, nodeset));\r
212                     }\r
213                 }\r
214             }\r
215             db.release(conn);\r
216         } catch (SQLException e) {\r
217             intlogger.error("SQLException " + e.getMessage());\r
218         }\r
219         return rv;\r
220     }\r
221 \r
222     public IngressRoute(int seq, int feedid, String user, String subnet, Collection<String> nodes)\r
223             throws IllegalArgumentException {\r
224         this(seq, feedid, user, subnet);\r
225         this.nodelist = -1;\r
226         this.nodes = new TreeSet<String>(nodes);\r
227     }\r
228 \r
229     public IngressRoute(int seq, int feedid, String user, String subnet, int nodeset)\r
230             throws IllegalArgumentException {\r
231         this(seq, feedid, user, subnet);\r
232         this.nodelist = nodeset;\r
233         this.nodes = new TreeSet<String>(readNodes());\r
234     }\r
235 \r
236     private IngressRoute(int seq, int feedid, String user, String subnet)\r
237             throws IllegalArgumentException {\r
238         this.seq = seq;\r
239         this.feedid = feedid;\r
240         this.userid = (user == null) ? "-" : user;\r
241         this.subnet = (subnet == null) ? "-" : subnet;\r
242         this.nodelist = -1;\r
243         this.nodes = null;\r
244         if (Feed.getFeedById(feedid) == null) {\r
245             throw new IllegalArgumentException("No such feed: " + feedid);\r
246         }\r
247         if (!this.subnet.equals("-")) {\r
248             SubnetMatcher sm = new SubnetMatcher(subnet);\r
249             if (!sm.isValid()) {\r
250                 throw new IllegalArgumentException("Invalid subnet: " + subnet);\r
251             }\r
252         }\r
253     }\r
254 \r
255     public IngressRoute(JSONObject jo) {\r
256         this.seq = jo.optInt("seq");\r
257         this.feedid = jo.optInt("feedid");\r
258         String t = jo.optString("user");\r
259         this.userid = t.equals("") ? "-" : t;\r
260         t = jo.optString("subnet");\r
261         this.subnet = t.equals("") ? "-" : t;\r
262         this.nodelist = -1;\r
263         this.nodes = new TreeSet<String>();\r
264         JSONArray ja = jo.getJSONArray("node");\r
265         for (int i = 0; i < ja.length(); i++) {\r
266             this.nodes.add(ja.getString(i));\r
267         }\r
268     }\r
269 \r
270     /**\r
271      * Does this particular IngressRoute match a request, represented by feedid and req? To match, <i>feedid</i> must\r
272      * match the feed ID in the route, the user in the route (if specified) must match the user in the request, and the\r
273      * subnet in the route (if specified) must match the subnet from the request.\r
274      *\r
275      * @param feedid the feedid for this request\r
276      * @param req the remainder of the request\r
277      * @return true if a match, false otherwise\r
278      */\r
279     public boolean matches(int feedid, HttpServletRequest req) {\r
280         // Check feedid\r
281         if (this.feedid != feedid) {\r
282             return false;\r
283         }\r
284 \r
285         // Get user from request and compare\r
286         // Note: we don't check the password; the node will do that\r
287         if (userid.length() > 0 && !userid.equals("-")) {\r
288             String credentials = req.getHeader("Authorization");\r
289             if (credentials == null || !credentials.startsWith("Basic ")) {\r
290                 return false;\r
291             }\r
292             String t = new String(Base64.decodeBase64(credentials.substring(6)));\r
293             int ix = t.indexOf(':');\r
294             if (ix >= 0) {\r
295                 t = t.substring(0, ix);\r
296             }\r
297             if (!t.equals(this.userid)) {\r
298                 return false;\r
299             }\r
300         }\r
301 \r
302         // If this route has a subnet, match it against the requester's IP addr\r
303         if (subnet.length() > 0 && !subnet.equals("-")) {\r
304             try {\r
305                 InetAddress inet = InetAddress.getByName(req.getRemoteAddr());\r
306                 SubnetMatcher sm = new SubnetMatcher(subnet);\r
307                 return sm.matches(inet.getAddress());\r
308             } catch (UnknownHostException e) {\r
309                 return false;\r
310             }\r
311         }\r
312         return true;\r
313     }\r
314 \r
315     /**\r
316      * Compare IP addresses as byte arrays to a subnet specified as a CIDR. Taken from\r
317      * org.onap.dmaap.datarouter.node.SubnetMatcher and modified somewhat.\r
318      */\r
319     public class SubnetMatcher {\r
320 \r
321         private byte[] sn;\r
322         private int len;\r
323         private int mask;\r
324         private boolean valid;\r
325 \r
326         /**\r
327          * Construct a subnet matcher given a CIDR\r
328          *\r
329          * @param subnet The CIDR to match\r
330          */\r
331         public SubnetMatcher(String subnet) {\r
332             int i = subnet.lastIndexOf('/');\r
333             if (i == -1) {\r
334                 try {\r
335                     sn = InetAddress.getByName(subnet).getAddress();\r
336                     len = sn.length;\r
337                     valid = true;\r
338                 } catch (UnknownHostException e) {\r
339                     len = 0;\r
340                     valid = false;\r
341                 }\r
342                 mask = 0;\r
343             } else {\r
344                 int n = Integer.parseInt(subnet.substring(i + 1));\r
345                 try {\r
346                     sn = InetAddress.getByName(subnet.substring(0, i)).getAddress();\r
347                     valid = true;\r
348                 } catch (UnknownHostException e) {\r
349                     valid = false;\r
350                 }\r
351                 len = n / 8;\r
352                 mask = ((0xff00) >> (n % 8)) & 0xff;\r
353             }\r
354         }\r
355 \r
356         public boolean isValid() {\r
357             return valid;\r
358         }\r
359 \r
360         /**\r
361          * Is the IP address in the CIDR?\r
362          *\r
363          * @param addr the IP address as bytes in network byte order\r
364          * @return true if the IP address matches.\r
365          */\r
366         public boolean matches(byte[] addr) {\r
367             if (!valid || addr.length != sn.length) {\r
368                 return false;\r
369             }\r
370             for (int i = 0; i < len; i++) {\r
371                 if (addr[i] != sn[i]) {\r
372                     return false;\r
373                 }\r
374             }\r
375             if (mask != 0 && ((addr[len] ^ sn[len]) & mask) != 0) {\r
376                 return false;\r
377             }\r
378             return true;\r
379         }\r
380     }\r
381 \r
382     /**\r
383      * Get the list of node names for this route.\r
384      *\r
385      * @return the list\r
386      */\r
387     public SortedSet<String> getNodes() {\r
388         return this.nodes;\r
389     }\r
390 \r
391     private Collection<String> readNodes() {\r
392         Collection<String> set = new TreeSet<String>();\r
393         try {\r
394             DB db = new DB();\r
395             @SuppressWarnings("resource")\r
396             Connection conn = db.getConnection();\r
397             String sql = "select NODEID from NODESETS where SETID = ?";\r
398             try (PreparedStatement ps = conn.prepareStatement(sql)) {\r
399                 ps.setInt(1, nodelist);\r
400                 try (ResultSet rs = ps.executeQuery()) {\r
401                     while (rs.next()) {\r
402                         int id = rs.getInt("NODEID");\r
403                         set.add(lookupNodeID(id));\r
404                     }\r
405                 }\r
406             }\r
407             db.release(conn);\r
408         } catch (SQLException e) {\r
409             intlogger.error("SQLException " + e.getMessage());\r
410         }\r
411         return set;\r
412     }\r
413 \r
414     /**\r
415      * Delete the IRT route having this IngressRoutes feed ID, user ID, and subnet from the database.\r
416      *\r
417      * @return true if the delete succeeded\r
418      */\r
419     @Override\r
420     public boolean doDelete(Connection c) {\r
421         boolean rv = true;\r
422         PreparedStatement ps = null;\r
423         try {\r
424             ps = c.prepareStatement("delete from INGRESS_ROUTES where FEEDID = ? and USERID = ? and SUBNET = ?");\r
425             ps.setInt(1, feedid);\r
426             ps.setString(2, userid);\r
427             ps.setString(3, subnet);\r
428             ps.execute();\r
429             ps.close();\r
430 \r
431             ps = c.prepareStatement("delete from NODESETS where SETID = ?");\r
432             ps.setInt(1, nodelist);\r
433             ps.execute();\r
434         } catch (SQLException e) {\r
435             rv = false;\r
436             intlogger.warn("PROV0007 doDelete: " + e.getMessage());\r
437         } finally {\r
438             try {\r
439                 if (ps != null) {\r
440                     ps.close();\r
441                 }\r
442             } catch (SQLException e) {\r
443                 intlogger.error("SQLException " + e.getMessage());\r
444             }\r
445         }\r
446         return rv;\r
447     }\r
448 \r
449     @SuppressWarnings("resource")\r
450     @Override\r
451     public boolean doInsert(Connection c) {\r
452         boolean rv = false;\r
453         PreparedStatement ps = null;\r
454         try {\r
455             // Create the NODESETS rows & set nodelist\r
456             int set = getMaxNodeSetID() + 1;\r
457             this.nodelist = set;\r
458             for (String node : nodes) {\r
459                 int id = lookupNodeName(node);\r
460                 ps = c.prepareStatement("insert into NODESETS (SETID, NODEID) values (?,?)");\r
461                 ps.setInt(1, this.nodelist);\r
462                 ps.setInt(2, id);\r
463                 ps.execute();\r
464                 ps.close();\r
465             }\r
466 \r
467             // Create the INGRESS_ROUTES row\r
468             ps = c.prepareStatement(\r
469                     "insert into INGRESS_ROUTES (SEQUENCE, FEEDID, USERID, SUBNET, NODESET) values (?, ?, ?, ?, ?)");\r
470             ps.setInt(1, this.seq);\r
471             ps.setInt(2, this.feedid);\r
472             ps.setString(3, this.userid);\r
473             ps.setString(4, this.subnet);\r
474             ps.setInt(5, this.nodelist);\r
475             ps.execute();\r
476             ps.close();\r
477             rv = true;\r
478         } catch (SQLException e) {\r
479             intlogger.warn("PROV0005 doInsert: " + e.getMessage());\r
480         } finally {\r
481             try {\r
482                 if (ps != null) {\r
483                     ps.close();\r
484                 }\r
485             } catch (SQLException e) {\r
486                 intlogger.error("SQLException " + e.getMessage());\r
487             }\r
488         }\r
489         return rv;\r
490     }\r
491 \r
492     @Override\r
493     public boolean doUpdate(Connection c) {\r
494         return doDelete(c) && doInsert(c);\r
495     }\r
496 \r
497     @Override\r
498     public JSONObject asJSONObject() {\r
499         JSONObject jo = new JSONObject();\r
500         jo.put("feedid", feedid);\r
501         // Note: for user and subnet, null, "", and "-" are equivalent\r
502         if (userid != null && !userid.equals("-") && !userid.equals("")) {\r
503             jo.put("user", userid);\r
504         }\r
505         if (subnet != null && !subnet.equals("-") && !subnet.equals("")) {\r
506             jo.put("subnet", subnet);\r
507         }\r
508         jo.put("seq", seq);\r
509         jo.put("node", nodes);\r
510         return jo;\r
511     }\r
512 \r
513     @Override\r
514     public String getKey() {\r
515         return String\r
516                 .format("%d/%s/%s/%d", feedid, (userid == null) ? "" : userid, (subnet == null) ? "" : subnet, seq);\r
517     }\r
518 \r
519     @Override\r
520     public int hashCode() {\r
521         return toString().hashCode();\r
522     }\r
523 \r
524     @Override\r
525     public boolean equals(Object obj) {\r
526         try {\r
527             if (!(obj instanceof IngressRoute)) {\r
528                 return false;\r
529             }\r
530             return this.compareTo((IngressRoute) obj) == 0;\r
531         } catch (NullPointerException e) {\r
532             return false;\r
533         }\r
534     }\r
535 \r
536     @Override\r
537     public int compareTo(IngressRoute in) {\r
538         if (in == null) {\r
539             throw new NullPointerException();\r
540         }\r
541         int n = this.feedid - in.feedid;\r
542         if (n != 0) {\r
543             return n;\r
544         }\r
545         n = this.seq - in.seq;\r
546         if (n != 0) {\r
547             return n;\r
548         }\r
549         n = this.userid.compareTo(in.userid);\r
550         if (n != 0) {\r
551             return n;\r
552         }\r
553         n = this.subnet.compareTo(in.subnet);\r
554         if (n != 0) {\r
555             return n;\r
556         }\r
557         return this.nodes.equals(in.nodes) ? 0 : 1;\r
558     }\r
559 \r
560     @Override\r
561     public String toString() {\r
562         return String.format("INGRESS: feed=%d, userid=%s, subnet=%s, seq=%d", feedid, (userid == null) ? "" : userid,\r
563                 (subnet == null) ? "" : subnet, seq);\r
564     }\r
565 }\r