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