1 package fr.ifremer.adagio.synchro.meta;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import static org.nuiton.i18n.I18n.t;
27
28 import java.lang.reflect.Field;
29 import java.sql.Connection;
30 import java.sql.DatabaseMetaData;
31 import java.sql.ResultSet;
32 import java.sql.SQLException;
33 import java.sql.Statement;
34 import java.util.Collection;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38
39 import org.apache.commons.collections4.CollectionUtils;
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42 import org.hibernate.HibernateException;
43 import org.hibernate.cfg.AvailableSettings;
44 import org.hibernate.cfg.Configuration;
45 import org.hibernate.cfg.Environment;
46 import org.hibernate.dialect.Dialect;
47 import org.hibernate.exception.spi.SQLExceptionConverter;
48 import org.hibernate.internal.util.StringHelper;
49 import org.hibernate.internal.util.config.ConfigurationHelper;
50 import org.hibernate.mapping.Table;
51 import org.hibernate.tool.hbm2ddl.DatabaseMetadata;
52 import org.hibernate.tool.hbm2ddl.TableMetadata;
53
54 import com.google.common.base.Preconditions;
55 import com.google.common.base.Predicate;
56 import com.google.common.collect.Lists;
57 import com.google.common.collect.Maps;
58 import com.google.common.collect.Sets;
59
60 import fr.ifremer.adagio.synchro.SynchroTechnicalException;
61 import fr.ifremer.adagio.synchro.dao.DaoUtils;
62 import fr.ifremer.adagio.synchro.intercept.SynchroInterceptor;
63 import fr.ifremer.adagio.synchro.intercept.SynchroInterceptorUtils;
64 import fr.ifremer.adagio.synchro.service.SynchroContext;
65
66
67
68
69
70
71
72 public class SynchroDatabaseMetadata {
73
74
75 private static final Log log =
76 LogFactory.getLog(SynchroDatabaseMetadata.class);
77
78 private static final String TABLE_CATALOG_PATTERN = "TABLE_CAT";
79 private static final String TABLE_TYPE_PATTERN = "TABLE_TYPE";
80 private static final String TABLE_SCHEMA_PATTERN = "TABLE_SCHEM";
81 private static final String REMARKS_PATTERN = "REMARKS";
82 private static final String TABLE_NAME_PATTERN = "TABLE_NAME";
83
84
85
86
87
88
89
90
91
92
93
94
95
96 public static SynchroDatabaseMetadata loadDatabaseMetadata(Connection connection,
97 Dialect dialect, Configuration configuration,
98 SynchroContext context,
99 Set<String> tableNames,
100 boolean enableJoinMetadataLoading
101 ) {
102 SynchroDatabaseMetadata result = new SynchroDatabaseMetadata(connection, dialect, configuration, context);
103 result.prepare(dialect, configuration, tableNames, null, null, enableJoinMetadataLoading);
104 return result;
105 }
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 public static SynchroDatabaseMetadata loadDatabaseMetadata(Connection connection,
124 Dialect dialect,
125 Configuration configuration,
126 SynchroContext context,
127 Set<String> tableNames,
128 Predicate<String> tableFilter,
129 Predicate<SynchroColumnMetadata> columnFilter,
130 boolean enableJoinMetadataLoading
131 ) {
132 SynchroDatabaseMetadata result = new SynchroDatabaseMetadata(connection, dialect, configuration, context);
133 result.prepare(dialect, configuration, tableNames, tableFilter, columnFilter, enableJoinMetadataLoading);
134 return result;
135 }
136
137 protected final DatabaseMetadata delegate;
138
139 protected final Map<String, SynchroTableMetadata> tables;
140
141 protected final DatabaseMetaData meta;
142
143 protected final Configuration configuration;
144
145 protected final Dialect dialect;
146
147 protected final Set<String> sequences;
148
149 protected final String[] types;
150
151 private SQLExceptionConverter sqlExceptionConverter;
152
153 protected List<SynchroInterceptor> interceptors;
154
155 protected SynchroContext context;
156
157 public SynchroDatabaseMetadata(Connection connection, Dialect dialect, Configuration configuration,
158 SynchroContext context) {
159 Preconditions.checkNotNull(connection);
160 Preconditions.checkNotNull(dialect);
161 Preconditions.checkNotNull(configuration);
162
163 this.configuration = configuration;
164 this.dialect = dialect;
165 this.sqlExceptionConverter = DaoUtils.newSQLExceptionConverter(dialect);
166 this.context = context;
167
168 try {
169 this.delegate = new DatabaseMetadata(connection, dialect, configuration, true);
170
171 Field sqlExceptionConverterField = DatabaseMetadata.class.getDeclaredField("sqlExceptionConverter");
172 sqlExceptionConverterField.setAccessible(true);
173 sqlExceptionConverterField.set(this.delegate, sqlExceptionConverter);
174
175 sequences = initSequences(connection, dialect);
176
177 Field typesField = DatabaseMetadata.class.getDeclaredField("types");
178 typesField.setAccessible(true);
179 this.types = (String[]) typesField.get(this.delegate);
180
181 this.meta = connection.getMetaData();
182
183 } catch (SQLException e) {
184 throw new SynchroTechnicalException(t("adagio.persistence.dbMetadata.instanciation.error", connection), e);
185 } catch (Exception e) {
186 throw new SynchroTechnicalException(t("adagio.persistence.dbMetadata.instanciation.error", connection), e);
187 }
188 tables = Maps.newTreeMap();
189 }
190
191 public int getTableCount() {
192 return tables.size();
193 }
194
195 public SynchroContext getContext() {
196 return this.context;
197 }
198
199 public Dialect getDialect() {
200 return this.dialect;
201 }
202
203 public int getInExpressionCountLimit() {
204 return dialect.getInExpressionCountLimit();
205 }
206
207 public boolean isSequence(String tableName) {
208 String[] strings = StringHelper.split(".", (String) tableName);
209 return sequences.contains(StringHelper.toLowerCase(strings[strings.length - 1]));
210 }
211
212 public void prepare(Dialect dialect,
213 Configuration configuration,
214 Set<String> tableNames,
215 Predicate<String> tableFilter,
216 Predicate<SynchroColumnMetadata> columnFilter,
217 boolean enableJoinMetadataLoading) {
218 Preconditions.checkArgument(CollectionUtils.isNotEmpty(tableNames) || tableFilter != null,
219 "One of 'tableNames' or 'tableFilter' must be set and not empty");
220
221
222 boolean enableFilter = tableFilter != null;
223 if (!enableFilter) {
224 for (String tablePattern : tableNames) {
225 enableFilter = tablePattern.contains("%");
226 if (enableFilter)
227 break;
228 }
229 }
230
231 Set<String> filteredTableNames = tableNames;
232 if (enableFilter) {
233 if (CollectionUtils.isEmpty(tableNames)) {
234 filteredTableNames = getTableNames(tableFilter);
235 }
236 else {
237 filteredTableNames = getTableNames(tableNames, tableFilter);
238 }
239 }
240
241
242 String jdbcCatalog = configuration.getProperty(Environment.DEFAULT_CATALOG);
243 String jdbcSchema = configuration.getProperty(Environment.DEFAULT_SCHEMA);
244
245 for (String tableName : filteredTableNames) {
246
247 if (log.isDebugEnabled()) {
248 log.debug("Load metas of table: " + tableName);
249 }
250
251 getTable(dialect, tableName, jdbcSchema, jdbcCatalog, false, columnFilter, false);
252 }
253
254 Map<String, SynchroTableMetadata> tablesByNames = Maps.newHashMap();
255 for (SynchroTableMetadata table : tables.values()) {
256 tablesByNames.put(table.getName(), table);
257
258
259 if (enableJoinMetadataLoading) {
260
261 if (log.isDebugEnabled()) {
262 log.debug("Load joins of table: " + table.getName());
263 }
264 table.initJoins(this);
265 }
266
267 fireOnTableLoad(table);
268 }
269 }
270
271 public SynchroTableMetadata getTable(String name) throws HibernateException {
272 String defaultSchema = ConfigurationHelper.getString(AvailableSettings.DEFAULT_SCHEMA, configuration.getProperties());
273 String defaultCatalog = ConfigurationHelper.getString(AvailableSettings.DEFAULT_CATALOG, configuration.getProperties());
274
275 return getTable(this.dialect, name, defaultSchema, defaultCatalog, false, null, true);
276 }
277
278 public SynchroTableMetadata getLoadedTable(String name) throws HibernateException {
279 String defaultSchema = ConfigurationHelper.getString(AvailableSettings.DEFAULT_SCHEMA, configuration.getProperties());
280 String defaultCatalog = ConfigurationHelper.getString(AvailableSettings.DEFAULT_CATALOG, configuration.getProperties());
281 return getLoadedTable(name, defaultSchema, defaultCatalog);
282 }
283
284 public SynchroTableMetadata getLoadedTable(String name,
285 String schema,
286 String catalog) throws HibernateException {
287 String key = Table.qualify(catalog, schema, name).toLowerCase();
288 return tables.get(key);
289 }
290
291
292
293
294
295
296
297
298
299
300 public Set<String> getTableNames(Predicate<String> tableFilter) {
301 return getTableNames(Sets.newHashSet("%"), tableFilter);
302 }
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319 public Set<String> getTableNames(Set<String> tablePatterns, Predicate<String> tableFilter) {
320 Preconditions.checkArgument(CollectionUtils.isNotEmpty(tablePatterns));
321
322 Set<String> tablenames = Sets.newHashSet();
323
324 String defaultSchema = ConfigurationHelper.getString(AvailableSettings.DEFAULT_SCHEMA, configuration.getProperties());
325 String defaultCatalog = ConfigurationHelper.getString(AvailableSettings.DEFAULT_CATALOG, configuration.getProperties());
326
327 String[] types = null;
328
329 if (configuration != null
330 && ConfigurationHelper.getBoolean(AvailableSettings.ENABLE_SYNONYMS, configuration.getProperties(), false)) {
331 types = new String[] { "TABLE", "VIEW", "SYNONYM" };
332 }
333 else {
334 types = new String[] { "TABLE", "VIEW" };
335 }
336
337 ResultSet res = null;
338 try {
339 if (log.isDebugEnabled()) {
340 log.debug("Getting table names, using filter");
341 }
342
343 for (String tablePattern : tablePatterns) {
344
345 res = meta.getTables(defaultCatalog, defaultSchema, tablePattern, types);
346 while (res.next()) {
347 String tableName = res.getString(TABLE_NAME_PATTERN);
348 if (!delegate.isSequence(tableName) && (tableFilter == null || tableFilter.apply(tableName))) {
349 if (log.isTraceEnabled()) {
350 log.trace(" " + TABLE_CATALOG_PATTERN + "=" + res.getString(TABLE_CATALOG_PATTERN)
351 + " " + TABLE_SCHEMA_PATTERN + "=" + res.getString(TABLE_SCHEMA_PATTERN)
352 + " " + TABLE_NAME_PATTERN + "=" + res.getString(TABLE_NAME_PATTERN)
353 + " " + TABLE_TYPE_PATTERN + "=" + res.getString(TABLE_TYPE_PATTERN)
354 + " " + REMARKS_PATTERN + "=" + res.getString(REMARKS_PATTERN));
355 }
356 tablenames.add(tableName);
357 }
358 }
359 }
360 } catch (SQLException e) {
361 throw sqlExceptionConverter.convert(e, "Retrieving database table names", "n/a");
362 } finally {
363 DaoUtils.closeSilently(res);
364 }
365
366 return tablenames;
367 }
368
369
370
371
372
373
374
375 public Set<String> getLoadedRootTableNames() {
376 Set<String> tablenames = Sets.newHashSet();
377 for (SynchroTableMetadata table : tables.values()) {
378 if (table.isRoot()) {
379 tablenames.add(table.getName());
380 }
381 }
382
383 return tablenames;
384 }
385
386
387
388
389
390
391
392 public Set<String> getLoadedTableNames() {
393 Set<String> tablenames = Sets.newHashSet();
394 for (SynchroTableMetadata table : tables.values()) {
395 tablenames.add(table.getName());
396 }
397
398 return tablenames;
399 }
400
401
402
403
404 public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
405 return meta.getExportedKeys(catalog, schema, table);
406 }
407
408
409
410
411 public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
412 return meta.getImportedKeys(catalog, schema, table);
413 }
414
415
416
417
418 public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
419 return meta.getPrimaryKeys(catalog, schema, table);
420 }
421
422
423
424 protected SynchroTableMetadata getTable(
425 Dialect dialect,
426 String name,
427 String schema,
428 String catalog,
429 boolean isQuoted,
430 Predicate<SynchroColumnMetadata> columnFilter,
431 boolean withJoinedTables) throws HibernateException {
432 String key = Table.qualify(catalog, schema, name).toLowerCase();
433 SynchroTableMetadata synchroTableMetadata = tables.get(key);
434 if (synchroTableMetadata == null) {
435
436 TableMetadata tableMetadata = delegate.getTableMetadata(
437 name.toLowerCase(), schema, catalog, isQuoted);
438 Preconditions.checkNotNull(tableMetadata, String.format("Could not find db table '%s' (schema=%s, catalog=%s)", name, schema, catalog));
439
440 List<SynchroInterceptor> interceptors = getInterceptors(tableMetadata);
441
442 synchroTableMetadata = new SynchroTableMetadata(
443 this,
444 tableMetadata,
445 interceptors,
446 name, sequences, columnFilter);
447 Preconditions.checkNotNull(synchroTableMetadata,
448 "Could not load metadata for table: " + name);
449
450 tables.put(key, synchroTableMetadata);
451 }
452 return synchroTableMetadata;
453 }
454
455 protected Set<String> initSequences(Connection connection, Dialect dialect) throws SQLException {
456 Set<String> sequences = Sets.newHashSet();
457 if (dialect.supportsSequences()) {
458 String sql = dialect.getQuerySequencesString();
459 if (sql != null) {
460
461 Statement statement = null;
462 ResultSet rs = null;
463 try {
464 statement = connection.createStatement();
465 rs = statement.executeQuery(sql);
466
467 while (rs.next()) {
468 sequences.add(StringHelper.toLowerCase(rs.getString(1)).trim());
469 }
470 } finally {
471 rs.close();
472 statement.close();
473 }
474
475 }
476 }
477 return sequences;
478 }
479
480 protected List<SynchroInterceptor> getInterceptors(final TableMetadata table) {
481 if (interceptors == null) {
482 interceptors = SynchroInterceptorUtils.load(SynchroInterceptor.class, this.context);
483 }
484
485 Collection<SynchroInterceptor> filteredInterceptors = SynchroInterceptorUtils.filter(
486 interceptors,
487 this,
488 table
489 );
490
491 return Lists.newArrayList(filteredInterceptors);
492 }
493
494 protected void fireOnTableLoad(SynchroTableMetadata table) {
495 List<SynchroInterceptor> interceptors = table.getInterceptors();
496 if (CollectionUtils.isNotEmpty(interceptors)) {
497 for (SynchroInterceptor interceptor : interceptors) {
498 interceptor.onTableLoad(table);
499 }
500 }
501 }
502 }