1 package net.avcompris.commons3.dao.impl;
2
3 import static com.google.common.base.Preconditions.checkNotNull;
4 import static net.avcompris.commons3.databeans.DataBeans.instantiate;
5
6 import java.sql.Connection;
7 import java.sql.DatabaseMetaData;
8 import java.sql.ResultSet;
9 import java.sql.SQLException;
10 import java.sql.Types;
11
12 import javax.sql.DataSource;
13
14 import org.apache.commons.lang3.NotImplementedException;
15
16 import net.avcompris.commons3.dao.DbTable;
17 import net.avcompris.commons3.dao.DbTablesUtils.Column;
18
19 public abstract class HealthCheckDbUtils {
20
21 public static void populateRuntimeDbStatus(
22 final MutableHealthCheckDb healthCheck,
23 final DataSource dataSource,
24 final String dbTableNamePrefix,
25 final DbTable... dbTables) {
26
27 checkNotNull(healthCheck, "healthCheck");
28 checkNotNull(dbTables, "dbTables");
29
30 final Context context = new Context(dataSource, dbTableNamePrefix);
31
32 final MutableHealthCheckDb.RuntimeDbStatus runtimeDbStatus = instantiate(
33 MutableHealthCheckDb.RuntimeDbStatus.class)
34 .setOk(true);
35
36 healthCheck.setRuntimeDbStatus(runtimeDbStatus);
37
38 for (final DbTable dbTable : dbTables) {
39
40 populateRuntimeDbStatus(healthCheck, runtimeDbStatus, context, dbTable);
41
42 for (final DbTable subTable : dbTable.getClass().getEnumConstants()) {
43
44 if (dbTable.equals(subTable) || !subTable.name().startsWith(dbTable.name() + "_")) {
45 continue;
46 }
47
48 populateRuntimeDbStatus(healthCheck, runtimeDbStatus, context, subTable);
49 }
50 }
51 }
52
53 private static void populateRuntimeDbStatus(
54 final MutableHealthCheckDb healthCheck,
55 final MutableHealthCheckDb.RuntimeDbStatus runtimeDbStatus,
56 final Context context,
57 final DbTable dbTable) {
58
59 final String runtimeDbTableName = dbTable.getRuntimeDbTableNameWithPrefix(context.dbTableNamePrefix);
60
61 final MutableHealthCheckDb.RuntimeDbTable runtimeDbTable = instantiate(
62 MutableHealthCheckDb.RuntimeDbTable.class)
63 .setOk(true)
64 .setRuntimeName(runtimeDbTableName)
65 .setCompileName(dbTable.name());
66
67 runtimeDbStatus.addToTables(runtimeDbTable);
68
69 final boolean existsInRuntimeDb;
70
71 try {
72
73 existsInRuntimeDb = context.existsInRuntimeDb(runtimeDbTableName);
74
75 } catch (final SQLException e) {
76
77 runtimeDbTable
78 .setExistsInRuntimeDb(false)
79 .setOk(false);
80
81 runtimeDbStatus
82 .setOk(false);
83
84 healthCheck
85 .addToErrors(e.toString())
86 .setOk(false);
87
88 return;
89 }
90
91 runtimeDbTable.setExistsInRuntimeDb(existsInRuntimeDb);
92
93 if (!existsInRuntimeDb) {
94
95 runtimeDbTable
96 .setOk(false);
97
98 runtimeDbStatus
99 .setOk(false);
100
101 healthCheck
102 .addToErrors("Could not find table in runtime DB: " + runtimeDbTableName)
103 .setOk(false);
104 }
105
106 for (final Column column : dbTable.columns()) {
107
108 final String columnName = column.getColumnName();
109
110 final String compileLiteral = toCompileLiteral(column);
111
112 final MutableHealthCheckDb.RuntimeDbColumn runtimeDbColumn = instantiate(
113 MutableHealthCheckDb.RuntimeDbColumn.class)
114 .setOk(true)
115 .setName(columnName)
116 .setCompileLiteral(compileLiteral);
117
118 runtimeDbTable.addToColumns(runtimeDbColumn);
119
120 final String runtimeLiteral;
121
122 try {
123
124 runtimeLiteral = context.getRuntimeLiteral(runtimeDbTableName, columnName);
125
126 } catch (final SQLException e) {
127
128 runtimeDbColumn
129 .setOk(false);
130
131 runtimeDbTable
132 .setOk(false);
133
134 runtimeDbStatus
135 .setOk(false);
136
137 healthCheck
138 .addToErrors(e.toString() + ", for: " + runtimeDbTableName + "." + columnName)
139 .setOk(false);
140
141 continue;
142 }
143
144 runtimeDbColumn.setRuntimeLiteral(runtimeLiteral);
145
146 if (!compileLiteral.equals(runtimeLiteral)) {
147
148 runtimeDbColumn
149 .setOk(false);
150
151 if (existsInRuntimeDb) {
152
153 runtimeDbTable
154 .setOk(false);
155
156 runtimeDbStatus
157 .setOk(false);
158
159 healthCheck
160 .addToErrors("Column runtime differs from spec: "
161 + runtimeDbTableName + "." + columnName + ": "
162 + runtimeLiteral + " ≠ " + compileLiteral)
163 .setOk(false);
164 }
165 }
166 }
167 }
168
169 private static String toCompileLiteral(final Column column) {
170
171 checkNotNull(column, "column");
172
173 final StringBuilder sb = new StringBuilder();
174
175 switch (column.getType()) {
176
177 case VARCHAR:
178 sb.append("VARCHAR(").append(column.getSize()).append(")");
179 break;
180
181 case BOOLEAN:
182 sb.append("BOOLEAN");
183 break;
184
185 case INTEGER:
186 sb.append("INTEGER");
187 break;
188
189 case TIMESTAMP_WITH_TIMEZONE:
190 sb.append("TIMESTAMPTZ");
191 break;
192
193 case TEXT:
194 sb.append("TEXT");
195 break;
196
197 case BYTE_ARRAY:
198 sb.append("BYTE_ARRAY");
199 break;
200
201 default:
202 throw new NotImplementedException("colum.type: " + column.getType()
203 + ", for: " + column.getColumnName());
204 }
205
206 if (column.isNotNull()) {
207 sb.append(" NOT NULL");
208 }
209
210 if (column.isPrimaryKey()) {
211 sb.append(" PRIMARY KEY");
212 }
213
214 return sb.toString();
215 }
216
217 private static final class Context {
218
219 private final DataSource dataSource;
220 public final String dbTableNamePrefix;
221
222 public Context(
223 final DataSource dataSource,
224 final String dbTableNamePrefix) {
225
226 this.dataSource = checkNotNull(dataSource, "dataSource");
227 this.dbTableNamePrefix = checkNotNull(dbTableNamePrefix, "dbTableNamePrefix");
228 }
229
230 public boolean existsInRuntimeDb(final String runtimeDbTableName) throws SQLException {
231
232 checkNotNull(runtimeDbTableName, "runtimeDbTableName");
233
234 try (Connection cxn = dataSource.getConnection()) {
235
236 try (ResultSet rs = cxn.getMetaData().getTables(null, null, runtimeDbTableName, null)) {
237
238 return rs.next();
239 }
240 }
241 }
242
243 public String getRuntimeLiteral(final String runtimeDbTableName, final String columnName) throws SQLException {
244
245 checkNotNull(runtimeDbTableName, "runtimeDbTableName");
246 checkNotNull(columnName, "columnName");
247
248 final StringBuilder sb = new StringBuilder();
249
250 try (Connection cxn = dataSource.getConnection()) {
251
252 try (ResultSet rs = cxn.getMetaData().getColumns(null, null, runtimeDbTableName, columnName)) {
253
254 if (!rs.next()) {
255 return null;
256 }
257
258 final int dataType = rs.getInt("DATA_TYPE");
259 final String typeName = rs.getString("TYPE_NAME");
260 final int size = rs.getInt("COLUMN_SIZE");
261 final boolean nullable = rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable;
262
263 switch (dataType) {
264
265 case Types.VARCHAR:
266 if ("text".equals(typeName)) {
267 sb.append("TEXT");
268 } else {
269 sb.append("VARCHAR(").append(size).append(")");
270 }
271 break;
272
273 case Types.BOOLEAN:
274 case Types.BIT:
275 sb.append("BOOLEAN");
276 break;
277
278 case Types.INTEGER:
279 sb.append("INTEGER");
280 break;
281
282 case Types.TIMESTAMP_WITH_TIMEZONE:
283 case Types.TIMESTAMP:
284 sb.append("TIMESTAMPTZ");
285 break;
286
287 case Types.BINARY:
288 sb.append("BYTE_ARRAY");
289 break;
290
291 default:
292 throw new NotImplementedException("dataType: " + dataType
293 + " (typeName: \"" + typeName + "\")"
294 + ", for: " + runtimeDbTableName + "." + columnName);
295 }
296
297 if (!nullable) {
298 sb.append(" NOT NULL");
299 }
300 }
301
302 boolean isPrimaryKey = false;
303
304 try (ResultSet rs = cxn.getMetaData().getPrimaryKeys(null, null, runtimeDbTableName)) {
305
306 while (rs.next()) {
307
308 final String runtimeDbColumnName = rs.getString("COLUMN_NAME");
309
310 if (columnName.contentEquals(runtimeDbColumnName)) {
311
312 isPrimaryKey = true;
313
314 break;
315 }
316 }
317 }
318
319 if (isPrimaryKey) {
320
321 sb.append(" PRIMARY KEY");
322 }
323 }
324
325 return sb.toString();
326 }
327 }
328 }