More unit test coverage and code cleanup
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / utils / DB.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.utils;\r
26 \r
27 import static java.lang.System.exit;\r
28 import static java.lang.System.getProperty;\r
29 \r
30 import com.att.eelf.configuration.EELFLogger;\r
31 import com.att.eelf.configuration.EELFManager;\r
32 import java.io.File;\r
33 import java.io.FileInputStream;\r
34 import java.io.FileReader;\r
35 import java.io.IOException;\r
36 import java.io.LineNumberReader;\r
37 import java.sql.Connection;\r
38 import java.sql.DatabaseMetaData;\r
39 import java.sql.DriverManager;\r
40 import java.sql.ResultSet;\r
41 import java.sql.SQLException;\r
42 import java.sql.Statement;\r
43 import java.util.HashSet;\r
44 import java.util.LinkedList;\r
45 import java.util.NoSuchElementException;\r
46 import java.util.Properties;\r
47 import java.util.Queue;\r
48 import java.util.Set;\r
49 \r
50 /**\r
51  * Load the DB JDBC driver, and manage a simple pool of connections to the DB.\r
52  *\r
53  * @author Robert Eby\r
54  * @version $Id$\r
55  */\r
56 public class DB {\r
57 \r
58     private static EELFLogger intlogger = EELFManager.getInstance().getLogger("InternalLog");\r
59 \r
60     private static String dbUrl;\r
61     private static String dbLogin;\r
62     private static String dbPassword;\r
63     private static Properties props;\r
64     private static final Queue<Connection> queue = new LinkedList<>();\r
65 \r
66     private static String httpsPort;\r
67     private static String httpPort;\r
68 \r
69     /**\r
70      * Construct a DB object.  If this is the very first creation of this object, it will load a copy of the properties\r
71      * for the server, and attempt to load the JDBC driver for the database. If a fatal error occurs (e.g. either the\r
72      * properties file or the DB driver is missing), the JVM will exit.\r
73      */\r
74     public DB() {\r
75         if (props == null) {\r
76             props = new Properties();\r
77             try {\r
78                 props.load(new FileInputStream(getProperty(\r
79                     "org.onap.dmaap.datarouter.provserver.properties",\r
80                     "/opt/app/datartr/etc/provserver.properties")));\r
81                 String dbDriver = (String) props.get("org.onap.dmaap.datarouter.db.driver");\r
82                 dbUrl = (String) props.get("org.onap.dmaap.datarouter.db.url");\r
83                 dbLogin = (String) props.get("org.onap.dmaap.datarouter.db.login");\r
84                 dbPassword = (String) props.get("org.onap.dmaap.datarouter.db.password");\r
85                 httpsPort = (String) props.get("org.onap.dmaap.datarouter.provserver.https.port");\r
86                 httpPort = (String) props.get("org.onap.dmaap.datarouter.provserver.http.port");\r
87                 Class.forName(dbDriver);\r
88             } catch (IOException e) {\r
89                 intlogger.error("PROV9003 Opening properties: " + e.getMessage(), e);\r
90                 exit(1);\r
91             } catch (ClassNotFoundException e) {\r
92                 intlogger.error("PROV9004 cannot find the DB driver: " + e);\r
93                 exit(1);\r
94             }\r
95         }\r
96     }\r
97 \r
98     /**\r
99      * Get the provisioning server properties (loaded from provserver.properties).\r
100      *\r
101      * @return the Properties object\r
102      */\r
103     public Properties getProperties() {\r
104         return props;\r
105     }\r
106 \r
107     /**\r
108      * Get a JDBC connection to the DB from the pool.  Creates a new one if none are available.\r
109      *\r
110      * @return the Connection\r
111      */\r
112     public Connection getConnection() throws SQLException {\r
113         Connection connection = null;\r
114         while (connection == null) {\r
115             synchronized (queue) {\r
116                 try {\r
117                     connection = queue.remove();\r
118                 } catch (NoSuchElementException nseEx) {\r
119                     intlogger.error("PROV9006 No connection on queue: " + nseEx.getMessage(), nseEx);\r
120                     int n = 0;\r
121                     do {\r
122                         // Try up to 3 times to get a connection\r
123                         try {\r
124                             connection = DriverManager.getConnection(dbUrl, dbLogin, dbPassword);\r
125                         } catch (SQLException sqlEx) {\r
126                             if (++n >= 3) {\r
127                                 throw sqlEx;\r
128                             }\r
129                         }\r
130                     } while (connection == null);\r
131                 }\r
132             }\r
133             if (connection != null && !connection.isValid(1)) {\r
134                 connection.close();\r
135                 connection = null;\r
136             }\r
137         }\r
138         return connection;\r
139     }\r
140 \r
141     /**\r
142      * Returns a JDBC connection to the pool.\r
143      *\r
144      * @param connection the Connection to return\r
145      */\r
146     public void release(Connection connection) {\r
147         if (connection != null) {\r
148             synchronized (queue) {\r
149                 if (!queue.contains(connection)) {\r
150                     queue.add(connection);\r
151                 }\r
152             }\r
153         }\r
154     }\r
155 \r
156     /**\r
157      * Run all necessary retrofits required to bring the database up to the level required for this version of the\r
158      * provisioning server.  This should be run before the server itself is started.\r
159      *\r
160      * @return true if all retrofits worked, false otherwise\r
161      */\r
162     public boolean runRetroFits() {\r
163         return retroFit1();\r
164     }\r
165 \r
166 \r
167     public static String getHttpsPort() {\r
168         return httpsPort;\r
169     }\r
170 \r
171     public static String getHttpPort() {\r
172         return httpPort;\r
173     }\r
174 \r
175     /**\r
176      * Retrofit 1 - Make sure the expected tables are in DB and are initialized. Uses sql_init_01.sql to setup the DB.\r
177      *\r
178      * @return true if the retrofit worked, false otherwise\r
179      */\r
180     private boolean retroFit1() {\r
181         final String[] expectedTables = {\r
182             "FEEDS", "FEED_ENDPOINT_ADDRS", "FEED_ENDPOINT_IDS", "PARAMETERS",\r
183             "SUBSCRIPTIONS", "LOG_RECORDS", "INGRESS_ROUTES", "EGRESS_ROUTES",\r
184             "NETWORK_ROUTES", "NODESETS", "NODES", "GROUPS"\r
185         };\r
186         Connection connection = null;\r
187         try {\r
188             connection = getConnection();\r
189             Set<String> actualTables = getTableSet(connection);\r
190             boolean initialize = false;\r
191             for (String tableName : expectedTables) {\r
192                 initialize |= !actualTables.contains(tableName);\r
193             }\r
194             if (initialize) {\r
195                 intlogger.info("PROV9001: First time startup; The database is being initialized.");\r
196                 runInitScript(connection, 1);\r
197             }\r
198         } catch (SQLException e) {\r
199             intlogger.error("PROV9000: The database credentials are not working: " + e.getMessage(), e);\r
200             return false;\r
201         } finally {\r
202             if (connection != null) {\r
203                 release(connection);\r
204             }\r
205         }\r
206         return true;\r
207     }\r
208 \r
209     /**\r
210      * Get a set of all table names in the DB.\r
211      *\r
212      * @param connection a DB connection\r
213      * @return the set of table names\r
214      */\r
215     private Set<String> getTableSet(Connection connection) {\r
216         Set<String> tables = new HashSet<>();\r
217         try {\r
218             DatabaseMetaData md = connection.getMetaData();\r
219             ResultSet rs = md.getTables(null, null, "%", null);\r
220             if (rs != null) {\r
221                 while (rs.next()) {\r
222                     tables.add(rs.getString("TABLE_NAME").toUpperCase());\r
223                 }\r
224                 rs.close();\r
225             }\r
226         } catch (SQLException e) {\r
227             intlogger.error("PROV9010: Failed to get TABLE data from DB: " + e.getMessage(), e);\r
228         }\r
229         return tables;\r
230     }\r
231 \r
232     /**\r
233      * Initialize the tables by running the initialization scripts located in the directory specified by the property\r
234      * <i>org.onap.dmaap.datarouter.provserver.dbscripts</i>.  Scripts have names of the form\r
235      * sql_init_NN.sql\r
236      *\r
237      * @param connection a DB connection\r
238      * @param scriptId the number of the sql_init_NN.sql script to run\r
239      */\r
240     private void runInitScript(Connection connection, int scriptId) {\r
241         String scriptDir = (String) props.get("org.onap.dmaap.datarouter.provserver.dbscripts");\r
242         String scriptFile = String.format("%s/sql_init_%02d.sql", scriptDir, scriptId);\r
243         if (!(new File(scriptFile)).exists()) {\r
244             intlogger.error("PROV9005 Failed to load sql script from : " + scriptFile);\r
245             exit(1);\r
246         }\r
247         try (LineNumberReader lineReader = new LineNumberReader(new FileReader(scriptFile));\r
248                 Statement statement = connection.createStatement()) {\r
249             StringBuilder strBuilder = new StringBuilder();\r
250             String line;\r
251             while ((line = lineReader.readLine()) != null) {\r
252                 if (!line.startsWith("--")) {\r
253                     line = line.trim();\r
254                     strBuilder.append(line);\r
255                     executeDdlStatement(statement, strBuilder, line);\r
256                 }\r
257             }\r
258             strBuilder.setLength(0);\r
259         } catch (Exception e) {\r
260             intlogger.error("PROV9002 Error when initializing table: " + e.getMessage(), e);\r
261             exit(1);\r
262         }\r
263     }\r
264 \r
265     private void executeDdlStatement(Statement statement, StringBuilder strBuilder, String line) throws SQLException {\r
266         if (line.endsWith(";")) {\r
267             // Execute one DDL statement\r
268             String sql = strBuilder.toString();\r
269             strBuilder.setLength(0);\r
270             statement.execute(sql);\r
271         }\r
272     }\r
273 }\r