View Javadoc
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 }