41 import java.util.EnumSet; |
41 import java.util.EnumSet; |
42 import java.util.HashSet; |
42 import java.util.HashSet; |
43 import java.util.List; |
43 import java.util.List; |
44 import java.util.ServiceLoader; |
44 import java.util.ServiceLoader; |
45 import java.util.Set; |
45 import java.util.Set; |
|
46 import java.util.concurrent.ExecutorService; |
|
47 import java.util.concurrent.Executors; |
|
48 import java.util.concurrent.TimeUnit; |
46 import java.util.logging.Level; |
49 import java.util.logging.Level; |
47 import java.util.logging.Logger; |
50 import java.util.logging.Logger; |
48 import javax.sql.rowset.RowSetMetaDataImpl; |
51 import javax.sql.rowset.RowSetMetaDataImpl; |
49 |
52 |
50 /** |
53 /** |
57 private static final Logger log = Logger.getLogger(InfoLister.class.getName()); |
60 private static final Logger log = Logger.getLogger(InfoLister.class.getName()); |
58 /** |
61 /** |
59 * Fake database name for output formatting |
62 * Fake database name for output formatting |
60 */ |
63 */ |
61 public static final String CONFIG_DB_NAME = "sqldk_configuration"; |
64 public static final String CONFIG_DB_NAME = "sqldk_configuration"; |
62 private PrintStream out; |
65 private final PrintStream out; |
63 private ConfigurationProvider configurationProvider; |
66 private final ConfigurationProvider configurationProvider; |
64 private CLIOptions options; |
67 private final CLIOptions options; |
65 private Formatter formatter; |
68 private Formatter formatter; |
66 |
69 |
67 public InfoLister(PrintStream out, ConfigurationProvider configurationProvider, CLIOptions options) { |
70 public InfoLister(PrintStream out, ConfigurationProvider configurationProvider, CLIOptions options) { |
68 this.out = out; |
71 this.out = out; |
69 this.configurationProvider = configurationProvider; |
72 this.configurationProvider = configurationProvider; |
129 |
132 |
130 for (FormatterDefinition fd : configurationProvider.getConfiguration().getFormatters()) { |
133 for (FormatterDefinition fd : configurationProvider.getConfiguration().getFormatters()) { |
131 data.add(new Object[]{fd.getName(), false, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)}); |
134 data.add(new Object[]{fd.getName(), false, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)}); |
132 } |
135 } |
133 |
136 |
134 printTable(formatter, header, data, "-- configured and built-in output formatters", null); |
137 printTable(formatter, header, "-- configured and built-in output formatters", null, data); |
135 } |
138 } |
136 |
139 |
137 private boolean isInstantiable(FormatterDefinition fd) { |
140 private boolean isInstantiable(FormatterDefinition fd) { |
138 try { |
141 try { |
139 try (ByteArrayOutputStream testStream = new ByteArrayOutputStream()) { |
142 try (ByteArrayOutputStream testStream = new ByteArrayOutputStream()) { |
150 ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("code", SQLType.INTEGER)); |
153 ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("code", SQLType.INTEGER)); |
151 List<Object[]> data = new ArrayList<>(); |
154 List<Object[]> data = new ArrayList<>(); |
152 for (SQLType sqlType : SQLType.values()) { |
155 for (SQLType sqlType : SQLType.values()) { |
153 data.add(new Object[]{sqlType.name(), sqlType.getCode()}); |
156 data.add(new Object[]{sqlType.name(), sqlType.getCode()}); |
154 } |
157 } |
155 printTable(formatter, header, data, "-- data types", null); |
158 printTable(formatter, header, "-- data types", null, data); |
156 log.log(Level.INFO, "Type names in --types option are case insensitive"); |
159 log.log(Level.INFO, "Type names in --types option are case insensitive"); |
157 } |
160 } |
158 |
161 |
159 public void listDatabases() throws ConfigurationException, FormatterException { |
162 public void listDatabases() throws ConfigurationException, FormatterException { |
160 ColumnsHeader header = constructHeader( |
163 ColumnsHeader header = constructHeader( |
170 for (DatabaseDefinition dd : configuredDatabases) { |
173 for (DatabaseDefinition dd : configuredDatabases) { |
171 data.add(new Object[]{dd.getName(), dd.getUserName(), dd.getUrl()}); |
174 data.add(new Object[]{dd.getName(), dd.getUserName(), dd.getUrl()}); |
172 } |
175 } |
173 } |
176 } |
174 |
177 |
175 printTable(formatter, header, data, "-- configured databases", null); |
178 printTable(formatter, header, "-- configured databases", null, data); |
176 } |
179 } |
177 |
180 |
178 public void listJdbcDrivers() throws FormatterException, ConfigurationException { |
181 public void listJdbcDrivers() throws FormatterException, ConfigurationException { |
179 ColumnsHeader header = constructHeader( |
182 ColumnsHeader header = constructHeader( |
180 new HeaderField("class", SQLType.VARCHAR), |
183 new HeaderField("class", SQLType.VARCHAR), |
193 d.getMinorVersion(), |
196 d.getMinorVersion(), |
194 d.jdbcCompliant() |
197 d.jdbcCompliant() |
195 }); |
198 }); |
196 } |
199 } |
197 |
200 |
198 printTable(formatter, header, data, "-- discovered JDBC drivers (available on the CLASSPATH)", null); |
201 printTable(formatter, header, "-- discovered JDBC drivers (available on the CLASSPATH)", null, data); |
199 } |
202 } |
200 |
203 |
201 public void listJdbcProperties() throws FormatterException, ConfigurationException { |
204 public void listJdbcProperties() throws FormatterException, ConfigurationException { |
202 for (String dbName : options.getDatabaseNamesToListProperties()) { |
205 for (String dbName : options.getDatabaseNamesToListProperties()) { |
203 ColumnsHeader header = constructHeader( |
206 ColumnsHeader header = constructHeader( |
255 parameters.add(new NamedParameter("databgase", dbName, SQLType.VARCHAR)); |
258 parameters.add(new NamedParameter("databgase", dbName, SQLType.VARCHAR)); |
256 parameters.add(new NamedParameter("driver_class", driver.getClass().getName(), SQLType.VARCHAR)); |
259 parameters.add(new NamedParameter("driver_class", driver.getClass().getName(), SQLType.VARCHAR)); |
257 parameters.add(new NamedParameter("driver_major_version", driver.getMajorVersion(), SQLType.INTEGER)); |
260 parameters.add(new NamedParameter("driver_major_version", driver.getMajorVersion(), SQLType.INTEGER)); |
258 parameters.add(new NamedParameter("driver_minor_version", driver.getMinorVersion(), SQLType.INTEGER)); |
261 parameters.add(new NamedParameter("driver_minor_version", driver.getMinorVersion(), SQLType.INTEGER)); |
259 |
262 |
260 printTable(formatter, header, data, "-- configured and configurable JDBC driver properties", parameters); |
263 printTable(formatter, header, "-- configured and configurable JDBC driver properties", parameters, data); |
261 } |
264 } |
262 } |
265 } |
263 |
266 |
264 } |
267 } |
265 |
268 |
275 } |
278 } |
276 } |
279 } |
277 return null; |
280 return null; |
278 } |
281 } |
279 |
282 |
280 public void testConnection() throws FormatterException, ConfigurationException { |
283 /** |
|
284 * Parallelism for connection testing – maximum concurrent database connections. |
|
285 */ |
|
286 private static final int TESTING_THREAD_COUNT = 64; |
|
287 /** |
|
288 * Time limit for all connection testing threads – particular timeouts per connection will be |
|
289 * much smaller. |
|
290 */ |
|
291 private static final long TESTING_AWAIT_LIMIT = 1; |
|
292 private static final TimeUnit TESTING_AWAIT_UNIT = TimeUnit.DAYS; |
|
293 |
|
294 public void testConnections() throws FormatterException, ConfigurationException { |
281 ColumnsHeader header = constructHeader( |
295 ColumnsHeader header = constructHeader( |
282 new HeaderField("database_name", SQLType.VARCHAR), |
296 new HeaderField("database_name", SQLType.VARCHAR), |
283 new HeaderField("configured", SQLType.BOOLEAN), |
297 new HeaderField("configured", SQLType.BOOLEAN), |
284 new HeaderField("connected", SQLType.BOOLEAN)); |
298 new HeaderField("connected", SQLType.BOOLEAN)); |
285 List<Object[]> data = new ArrayList<>(); |
299 |
286 |
300 log.log(Level.FINE, "Testing DB connections in {0} threads", TESTING_THREAD_COUNT); |
287 for (String dbName : options.getDatabaseNamesToTest()) { |
301 |
288 data.add(testConnection(dbName)); |
302 ExecutorService es = Executors.newFixedThreadPool(TESTING_THREAD_COUNT); |
289 } |
303 |
290 |
304 final Formatter currentFormatter = formatter; |
291 printTable(formatter, header, data, "-- database configuration and connectivity test", null); |
305 |
|
306 printHeader(currentFormatter, header, "-- database configuration and connectivity test", null); |
|
307 |
|
308 for (final String dbName : options.getDatabaseNamesToTest()) { |
|
309 es.submit(new Runnable() { |
|
310 // TODO: Java 8 – lambda |
|
311 @Override |
|
312 public void run() { |
|
313 final Object[] row = testConnection(dbName); |
|
314 synchronized (currentFormatter) { |
|
315 printRow(currentFormatter, row); |
|
316 } |
|
317 } |
|
318 }); |
|
319 } |
|
320 |
|
321 es.shutdown(); |
|
322 |
|
323 try { |
|
324 log.log(Level.FINEST, "Waiting for test results: {0} {1}", new Object[]{TESTING_AWAIT_LIMIT, TESTING_AWAIT_UNIT.name()}); |
|
325 boolean finished = es.awaitTermination(TESTING_AWAIT_LIMIT, TESTING_AWAIT_UNIT); |
|
326 if (finished) { |
|
327 log.log(Level.FINEST, "All testing threads finished in time limit."); |
|
328 } else { |
|
329 throw new FormatterException("Exceeded total time limit for test threads – this should never happen"); |
|
330 } |
|
331 } catch (InterruptedException e) { |
|
332 throw new FormatterException("Interrupted while waiting for test results", e); |
|
333 } |
|
334 |
|
335 printFooter(currentFormatter); |
292 } |
336 } |
293 |
337 |
294 public Object[] testConnection(String dbName) { |
338 public Object[] testConnection(String dbName) { |
295 log.log(Level.FINE, "Testing connection to database: {0}", dbName); |
339 log.log(Level.FINE, "Testing connection to database: {0}", dbName); |
296 |
340 |
304 try (DatabaseConnection dc = dd.connect(options.getDatabaseProperties())) { |
348 try (DatabaseConnection dc = dd.connect(options.getDatabaseProperties())) { |
305 succesfullyConnected = dc.test(); |
349 succesfullyConnected = dc.test(); |
306 } |
350 } |
307 log.log(Level.FINE, "Database connection test was successful"); |
351 log.log(Level.FINE, "Database connection test was successful"); |
308 } catch (ConfigurationException | SQLException e) { |
352 } catch (ConfigurationException | SQLException e) { |
309 log.log(Level.SEVERE, "Error during testing connection", e); |
353 log.log(Level.SEVERE, "Error during testing connection " + dbName, e); |
310 } |
354 } |
311 |
355 |
312 return new Object[]{dbName, succesfullyConfigured, succesfullyConnected}; |
356 return new Object[]{dbName, succesfullyConfigured, succesfullyConnected}; |
313 } |
357 } |
314 |
358 |
329 |
373 |
330 private void println(String line) { |
374 private void println(String line) { |
331 out.println(line); |
375 out.println(line); |
332 } |
376 } |
333 |
377 |
334 private void printTable(Formatter formatter, ColumnsHeader header, List<Object[]> data, String sql, List<Parameter> parameters) throws ConfigurationException, FormatterException { |
378 private void printTable(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters, List<Object[]> data) throws ConfigurationException, FormatterException { |
|
379 printHeader(formatter, header, sql, parameters); |
|
380 |
|
381 for (Object[] row : data) { |
|
382 printRow(formatter, row); |
|
383 } |
|
384 |
|
385 printFooter(formatter); |
|
386 } |
|
387 |
|
388 private void printHeader(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters) { |
335 formatter.writeStartStatement(); |
389 formatter.writeStartStatement(); |
336 |
|
337 if (sql != null) { |
390 if (sql != null) { |
338 formatter.writeQuery(sql); |
391 formatter.writeQuery(sql); |
339 if (parameters != null) { |
392 if (parameters != null) { |
340 formatter.writeParameters(parameters); |
393 formatter.writeParameters(parameters); |
341 } |
394 } |
342 } |
395 } |
343 |
|
344 formatter.writeStartResultSet(header); |
396 formatter.writeStartResultSet(header); |
345 |
397 } |
346 for (Object[] row : data) { |
398 |
347 formatter.writeStartRow(); |
399 private void printRow(Formatter formatter, Object[] row) { |
348 for (Object cell : row) { |
400 formatter.writeStartRow(); |
349 formatter.writeColumnValue(cell); |
401 for (Object cell : row) { |
350 } |
402 formatter.writeColumnValue(cell); |
351 formatter.writeEndRow(); |
403 } |
352 } |
404 formatter.writeEndRow(); |
353 |
405 } |
|
406 |
|
407 private void printFooter(Formatter formatter) { |
354 formatter.writeEndResultSet(); |
408 formatter.writeEndResultSet(); |
355 formatter.writeEndStatement(); |
409 formatter.writeEndStatement(); |
356 } |
410 } |
357 |
411 |
358 private Formatter getFormatter() throws ConfigurationException, FormatterException { |
412 private Formatter getFormatter() throws ConfigurationException, FormatterException { |
395 } |
449 } |
396 |
450 |
397 public enum InfoType { |
451 public enum InfoType { |
398 |
452 |
399 HELP { |
453 HELP { |
400 @Override |
454 @Override |
401 public void showInfo(InfoLister infoLister) { |
455 public void showInfo(InfoLister infoLister) { |
402 infoLister.printResource(Constants.HELP_FILE); |
456 infoLister.printResource(Constants.HELP_FILE); |
403 } |
457 } |
404 }, |
458 }, |
405 VERSION { |
459 VERSION { |
406 @Override |
460 @Override |
407 public void showInfo(InfoLister infoLister) { |
461 public void showInfo(InfoLister infoLister) { |
408 infoLister.printResource(Constants.VERSION_FILE); |
462 infoLister.printResource(Constants.VERSION_FILE); |
409 } |
463 } |
410 }, |
464 }, |
411 LICENSE { |
465 LICENSE { |
412 @Override |
466 @Override |
413 public void showInfo(InfoLister infoLister) { |
467 public void showInfo(InfoLister infoLister) { |
414 infoLister.printResource(Constants.LICENSE_FILE); |
468 infoLister.printResource(Constants.LICENSE_FILE); |
415 } |
469 } |
416 }, |
470 }, |
417 FORMATTERS { |
471 FORMATTERS { |
418 @Override |
472 @Override |
419 public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { |
473 public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { |
420 infoLister.listFormatters(); |
474 infoLister.listFormatters(); |
421 } |
475 } |
422 }, |
476 }, |
423 TYPES { |
477 TYPES { |
424 @Override |
478 @Override |
425 public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { |
479 public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { |
426 infoLister.listTypes(); |
480 infoLister.listTypes(); |
427 } |
481 } |
428 }, |
482 }, |
429 JDBC_DRIVERS { |
483 JDBC_DRIVERS { |
430 @Override |
484 @Override |
431 public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException { |
485 public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException { |
432 infoLister.listJdbcDrivers(); |
486 infoLister.listJdbcDrivers(); |
433 } |
487 } |
434 }, |
488 }, |
435 JDBC_PROPERTIES { |
489 JDBC_PROPERTIES { |
436 @Override |
490 @Override |
437 public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException { |
491 public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException { |
438 infoLister.listJdbcProperties(); |
492 infoLister.listJdbcProperties(); |
439 } |
493 } |
440 }, |
494 }, |
441 DATABASES { |
495 DATABASES { |
442 @Override |
496 @Override |
443 public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { |
497 public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { |
444 infoLister.listDatabases(); |
498 infoLister.listDatabases(); |
445 } |
499 } |
446 }, |
500 }, |
447 CONNECTION { |
501 CONNECTION { |
448 @Override |
502 @Override |
449 public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { |
503 public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { |
450 infoLister.testConnection(); |
504 infoLister.testConnections(); |
451 } |
505 } |
452 }; |
506 }; |
453 |
507 |
454 public abstract void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException; |
508 public abstract void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException; |
455 } |
509 } |
456 } |
510 } |