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