1
18 package net.bull.javamelody.internal.model;
19
20 import java.io.Serializable;
21 import java.sql.Connection;
22 import java.sql.DatabaseMetaData;
23 import java.sql.DriverManager;
24 import java.sql.PreparedStatement;
25 import java.sql.ResultSet;
26 import java.sql.ResultSetMetaData;
27 import java.sql.SQLException;
28 import java.sql.Statement;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.ResourceBundle;
36
37 import javax.naming.NamingException;
38 import javax.sql.DataSource;
39
40 import net.bull.javamelody.JdbcWrapper;
41 import net.bull.javamelody.Parameter;
42 import net.bull.javamelody.internal.common.I18N;
43 import net.bull.javamelody.internal.common.Parameters;
44
45
49 public class DatabaseInformations implements Serializable {
50 private static final long serialVersionUID = -6105478981257689782L;
51
52 enum Database {
53
54
55 POSTGRESQL("PostgreSQL"),
56 MYSQL("MySQL"),
57 MYSQL4("MySQL"),
58 MARIADB("MariaDB"),
59 ORACLE("Oracle"),
60 DB2("DB2 UDB for AS/400", "DB2/"),
61 H2("H2"),
62 HSQLDB("HSQL Database Engine"),
63 SQLSERVER("Microsoft SQL Server"),
64 SYBASE("Sybase SQL Server", "Adaptive Server Enterprise"),
65 INFORMIX("Informix Dynamic Server"),
66 SQLITE("SQLite");
67
68
69
70
71 private static final String RESOURCE_BUNDLE_BASE_NAME = Parameters
72 .getResourcePath("databaseInformations").replace('/', '.').substring(1);
73
74 private final List<String> databaseNames;
75
76 Database(String... databaseNames) {
77 this.databaseNames = Arrays.asList(databaseNames);
78 }
79
80
81 List<String> getRequestNames() {
82
83 final List<String> tmp;
84 switch (this) {
85 case POSTGRESQL:
86 tmp = Arrays.asList("pg_stat_activity", "pg_locks", "pg_database", "pg_tablespace",
87 "pg_stat_database", "pg_stat_user_tables", "pg_stat_user_indexes",
88 "pg_statio_user_tables", "pg_statio_user_indexes",
89 "pg_statio_user_sequences", "pg_settings");
90 break;
91 case MYSQL:
92 case MARIADB:
93 tmp = Arrays.asList("processlist", "databases", "variables", "global_status",
94 "innodb_status", "unusedIndexes", "longRunning", "tableStats",
95 "eventsWaits", "tableIoWaits", "indexIoWaits", "tableLockWaits",
96 "tablesWithoutPk", "perfDigests", "memory");
97 break;
98 case MYSQL4:
99
100
101 tmp = Arrays.asList("processlist", "databases", "variables", "global_status",
102 "innodb_status");
103 break;
104 case ORACLE:
105 tmp = Arrays.asList("sessions", "locks", "sqlTimes", "foreignKeysWithoutIndexes",
106 "invalidObjects", "disabledConstraints", "tableStats", "instance",
107 "database", "nlsParameters", "tablespaceFreespace", "datafileIo",
108 "tablespaceExtents", "ratios", "parameters", "rollbackSegmentStatistics",
109 "statistics", "events");
110 break;
111 case DB2:
112 tmp = Arrays.asList("mon_current_sql", "mon_db_summary", "mon_lockwaits",
113 "mon_service_subclass_summary", "mon_current_uow", "mon_workload_summary",
114 "mon_get_connection", "current_queries");
115 break;
116 case H2:
117 tmp = Arrays.asList("memory", "sessions", "locks", "settings");
118 break;
119 case HSQLDB:
120 tmp = Arrays.asList("system_sessions", "system_cacheinfo", "system_properties",
121 "system_schemas");
122 break;
123 case SQLSERVER:
124 tmp = Arrays.asList("version", "connections");
125 break;
126 case SYBASE:
127 tmp = Arrays.asList("sp_who", "connections", "sp_lock", "lock",
128 "running_stored_procedure", "used_temporary_tables", "used_tables",
129 "sp_version");
130 break;
131 case INFORMIX:
132 tmp = Arrays.asList("version", "sessions", "resources_by_user", "current_queries",
133 "config");
134 break;
135 case SQLITE:
136 tmp = Arrays.asList("version", "database_list");
137 break;
138 default:
139 throw new IllegalStateException();
140 }
141 return addPrefix(tmp);
142 }
143
144 private List<String> addPrefix(List<String> requests) {
145 final List<String> list = new ArrayList<>(requests.size());
146 final String prefix = this.toString().toLowerCase(Locale.ENGLISH) + '.';
147 for (final String requestName : requests) {
148 list.add(prefix + requestName);
149 }
150 return list;
151 }
152
153 String getUrlIdentifier() {
154 if (this == MYSQL4) {
155 return MYSQL.toString().toLowerCase(Locale.ENGLISH);
156 }
157 return this.toString().toLowerCase(Locale.ENGLISH);
158 }
159
160 String getRequestByName(String requestName) {
161 return ResourceBundle.getBundle(RESOURCE_BUNDLE_BASE_NAME).getString(requestName);
162 }
163
164 List<String> getDatabaseNames() {
165 return databaseNames;
166 }
167
168 private boolean isRecognized(String databaseName, String url) {
169 for (final String name : getDatabaseNames()) {
170 if (databaseName.startsWith(name)) {
171 return true;
172 }
173 }
174 return url != null && url.contains(getUrlIdentifier());
175 }
176
177 static Database getDatabaseForConnection(Connection connection) throws SQLException {
178 final DatabaseMetaData metaData = connection.getMetaData();
179 final String databaseName = metaData.getDatabaseProductName();
180 final String url = metaData.getURL();
181 for (final Database database : values()) {
182 if (database.isRecognized(databaseName, url)) {
183 if (database == MYSQL && metaData.getDatabaseMajorVersion() <= 4) {
184
185 return MYSQL4;
186 }
187 return database;
188 }
189 }
190 throw new IllegalArgumentException(
191 I18N.getFormattedString("type_base_de_donnees_inconnu", databaseName));
192 }
193 }
194
195 private final Database database;
196 @SuppressWarnings("all")
197 private final List<String> requestNames;
198 private final int selectedRequestIndex;
199 private final String[][] result;
200
201 public DatabaseInformations(int selectedRequestIndex) throws SQLException, NamingException {
202 super();
203 this.selectedRequestIndex = selectedRequestIndex;
204 final Connection connection = getConnection();
205 assert connection != null;
206 try {
207 database = Database.getDatabaseForConnection(connection);
208 requestNames = database.getRequestNames();
209 final String request = database
210 .getRequestByName(requestNames.get(selectedRequestIndex));
211 result = executeRequest(connection, request, null);
212 } finally {
213 connection.close();
214 }
215 }
216
217 public static int parseRequestIndex(String requestIndex) {
218 if (requestIndex != null) {
219 return Integer.parseInt(requestIndex);
220 }
221 return 0;
222 }
223
224 public int getNbColumns() {
225 final String selectedRequestName = getSelectedRequestName();
226 if ("oracle.statistics".equals(selectedRequestName)) {
227 return 2;
228 } else if ("oracle.events".equals(selectedRequestName)) {
229 return 2;
230 } else if ("mysql.variables".equals(selectedRequestName)) {
231 return 2;
232 } else if ("mysql.global_status".equals(selectedRequestName)) {
233 return 4;
234 } else if ("h2.settings".equals(selectedRequestName)) {
235 return 2;
236 }
237 return 1;
238 }
239
240 public int getSelectedRequestIndex() {
241 return selectedRequestIndex;
242 }
243
244 public String getSelectedRequestName() {
245 return requestNames.get(getSelectedRequestIndex());
246 }
247
248 public String[][] getResult() {
249 return result;
250 }
251
252 public List<String> getRequestNames() {
253 return requestNames;
254 }
255
256 private static String[][] executeRequest(Connection connection, String request,
257 List<?> parametersValues) throws SQLException {
258 try (PreparedStatement statement = connection.prepareStatement(request)) {
259 if (parametersValues != null) {
260 int i = 1;
261 for (final Object parameterValue : parametersValues) {
262 statement.setObject(i, parameterValue);
263 i++;
264 }
265 }
266 return executeQuery(statement);
267 } catch (final SQLException e) {
268 if (e.getErrorCode() == 942 && e.getMessage() != null
269 && e.getMessage().startsWith("ORA-")) {
270 final String userName = connection.getMetaData().getUserName();
271 final String message = I18N.getFormattedString("oracle.grantSelectAnyDictionnary",
272 userName);
273 throw new SQLException(message, e);
274 }
275 throw e;
276 }
277 }
278
279 private static String[][] executeQuery(PreparedStatement statement) throws SQLException {
280 try (ResultSet resultSet = statement.executeQuery()) {
281 final ResultSetMetaData metaData = resultSet.getMetaData();
282 final int columnCount = metaData.getColumnCount();
283 final List<String[]> list = new ArrayList<>();
284 String[] values = new String[columnCount];
285 for (int i = 1; i <= columnCount; i++) {
286 values[i - 1] = metaData.getColumnName(i) + '\n' + metaData.getColumnTypeName(i)
287 + '(' + metaData.getColumnDisplaySize(i) + ')';
288 }
289 list.add(values);
290
291 while (resultSet.next()) {
292 values = new String[columnCount];
293 for (int i = 1; i <= columnCount; i++) {
294 values[i - 1] = resultSet.getString(i);
295 }
296 list.add(values);
297 }
298 return list.toArray(new String[0][]);
299 }
300 }
301
302 private static Connection getConnection() throws SQLException, NamingException {
303
304
305 if (Parameters.getLastConnectUrl() != null) {
306 final Connection connection = DriverManager
307 .getConnection(Parameters.getLastConnectUrl(), Parameters.getLastConnectInfo());
308 connection.setAutoCommit(false);
309 return connection;
310 }
311
312
313
314 final Collection<DataSource> dataSources = JdbcWrapper.getJndiAndSpringDataSources()
315 .values();
316 for (final DataSource dataSource : dataSources) {
317 try {
318 return dataSource.getConnection();
319
320
321
322 } catch (final Exception e) {
323
324
325 continue;
326 }
327 }
328 if (!dataSources.isEmpty()) {
329
330 return dataSources.iterator().next().getConnection();
331 }
332 return null;
333 }
334
335 public static String explainPlanFor(String sqlRequest) throws SQLException, NamingException {
336 final Connection connection = getConnection();
337 if (connection != null) {
338 try {
339 final Database database = Database.getDatabaseForConnection(connection);
340 if (database == Database.ORACLE) {
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355 final String statementId = String.valueOf(sqlRequest.hashCode());
356 final String explainRequest = buildExplainRequest(sqlRequest, statementId);
357
358 try (Statement statement = connection.createStatement()) {
359 statement.execute(explainRequest);
360 }
361
362
363 return getPlanOutput(connection, statementId);
364 }
365 } finally {
366 if (!connection.getAutoCommit()) {
367 connection.rollback();
368 }
369 connection.close();
370 }
371 }
372 return null;
373 }
374
375 private static String buildExplainRequest(String sqlRequest, String statementId) {
376
377
378 int i = 1;
379 String request = sqlRequest;
380 if (Parameter.SQL_TRANSFORM_PATTERN.getValue() != null) {
381
382
383
384
385 request = request.replace(Counter.TRANSFORM_REPLACEMENT_CHAR, '?');
386 }
387
388
389
390
391 String explainRequest = "explain plan set statement_id = '" + statementId + "' for "
392 + request;
393
394
395
396
397 if (explainRequest.indexOf(';') != -1) {
398 explainRequest = explainRequest.substring(0, explainRequest.indexOf(';'));
399 }
400
401
402 int index = explainRequest.indexOf('?');
403 while (index != -1) {
404 explainRequest = explainRequest.substring(0, index) + ':' + i
405 + explainRequest.substring(index + 1);
406 i++;
407 index = explainRequest.indexOf('?');
408 }
409 return explainRequest;
410 }
411
412 private static String getPlanOutput(Connection connection, String statementId)
413 throws SQLException {
414
415 final String planTableRequest = "select * from table(dbms_xplan.display(null,?, null))";
416 final String[][] planTableOutput = executeRequest(connection, planTableRequest,
417 Collections.singletonList(statementId));
418 final StringBuilder sb = new StringBuilder();
419 for (final String[] row : planTableOutput) {
420 for (final String value : row) {
421 sb.append(value);
422 }
423 sb.append('\n');
424 }
425 if (sb.indexOf("-") != -1) {
426 sb.delete(0, sb.indexOf("-"));
427 }
428 return sb.toString();
429 }
430
431
432 @Override
433 public String toString() {
434 return getClass().getSimpleName() + "[database=" + database + ']';
435 }
436 }
437