1 package net.sumaris.core.dao.technical.schema;
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 com.google.common.base.Preconditions;
27 import com.google.common.collect.Maps;
28 import com.google.common.collect.Sets;
29 import net.sumaris.core.config.SumarisConfiguration;
30 import net.sumaris.core.dao.cache.CacheNames;
31 import org.apache.commons.lang3.StringUtils;
32 import org.hibernate.HibernateException;
33 import org.hibernate.boot.Metadata;
34 import org.hibernate.boot.model.naming.Identifier;
35 import org.hibernate.boot.model.relational.QualifiedTableName;
36 import org.hibernate.dialect.Dialect;
37 import org.hibernate.mapping.Column;
38 import org.hibernate.mapping.PersistentClass;
39 import org.hibernate.mapping.Property;
40 import org.hibernate.mapping.Table;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43 import org.springframework.beans.factory.BeanInitializationException;
44 import org.springframework.beans.factory.annotation.Autowired;
45 import org.springframework.cache.annotation.Cacheable;
46 import org.springframework.context.annotation.Lazy;
47 import org.springframework.jdbc.datasource.DataSourceUtils;
48 import org.springframework.stereotype.Component;
49
50 import javax.annotation.PostConstruct;
51 import javax.sql.DataSource;
52 import java.sql.*;
53 import java.util.HashSet;
54 import java.util.Iterator;
55 import java.util.Map;
56 import java.util.Set;
57
58
59
60
61
62
63 @Lazy
64 @Component(value = "sumarisDatabaseMetadata")
65 public class SumarisDatabaseMetadata {
66
67
68 private static final Logger log =
69 LoggerFactory.getLogger(SumarisDatabaseMetadata.class);
70
71 @Autowired
72 protected SumarisDatabaseMetadata databaseMetadata;
73
74 protected final Map<String, SumarisTableMetadata> tables;
75 protected final Map<String, PersistentClass> entities;
76
77 protected String defaultSchemaName = null;
78 protected String defaultCatalogName = null;
79 protected Dialect dialect = null;
80
81 protected DataSource dataSource = null;
82
83 protected final Metadata metadata;
84
85 protected Set<String> sequences;
86
87 protected String sequenceSuffix;
88
89 protected String defaultUpdateDateColumnName;
90
91 @Autowired
92 public SumarisDatabaseMetadata(DataSource dataSource, SumarisConfiguration configuration) {
93 super();
94 Preconditions.checkNotNull(dataSource);
95 Preconditions.checkNotNull(configuration);
96
97 this.dataSource = dataSource;
98 this.metadata = MetadataExtractorIntegrator.INSTANCE.getMetadata();
99 this.dialect = metadata.getDatabase().getDialect();
100
101 this.defaultSchemaName = configuration.getJdbcSchema();
102 this.defaultCatalogName = configuration.getJdbcCatalog();
103 this.sequenceSuffix = configuration.getSequenceSuffix();
104
105 this.defaultUpdateDateColumnName = "update_date";
106
107 tables = Maps.newTreeMap();
108 entities = Maps.newHashMap();
109
110
111 }
112
113 @Cacheable(cacheNames = CacheNames.TABLE_META_BY_NAME, key = "#name.toLowerCase()", unless = "#result == null")
114 public SumarisTableMetadata getTable(String name) throws HibernateException {
115 return getTable(name, defaultSchemaName, defaultCatalogName);
116 }
117
118 @Cacheable(cacheNames = CacheNames.TABLE_META_BY_NAME, key = "#name.toLowerCase()", unless = "#result == null")
119 public SumarisHibernateTableMetadata getHibernateTable(String name) throws HibernateException {
120 return (SumarisHibernateTableMetadata) getTable(name);
121 }
122
123 public int getTableCount() {
124 return tables.size();
125 }
126
127 public Set<String> getTableNames() {
128 HashSet<String> result = Sets.newHashSet();
129 for (SumarisTableMetadata tableMetadata : tables.values()) {
130 result.add(tableMetadata.getName());
131 }
132 return result;
133 }
134
135 public Set<String> getSequences() {
136 return sequences;
137 }
138
139 public String getSequenceSuffix() {
140 return sequenceSuffix;
141 }
142
143 public Dialect getDialect() {
144 return dialect;
145 }
146
147 public QualifiedTableName getQualifiedTableName(String catalog, String schema, String tableName) {
148 return new QualifiedTableName(
149 Identifier.toIdentifier(catalog),
150 Identifier.toIdentifier(schema),
151 Identifier.toIdentifier(tableName));
152 }
153
154 public String getDefaultUpdateDateColumnName() {
155 return defaultUpdateDateColumnName;
156 }
157
158
159
160 @PostConstruct
161 protected void init() {
162
163 Connection conn = null;
164 try {
165 conn = DataSourceUtils.getConnection(dataSource);
166
167
168 this.sequences = initSequences(conn, dialect);
169
170
171 initTables(conn);
172
173 }
174 catch(SQLException e) {
175 throw new BeanInitializationException("Could not init SumarisDatabaseMetadata", e);
176 }
177 finally {
178 DataSourceUtils.releaseConnection(conn, dataSource);
179 }
180 }
181
182
183
184
185
186
187
188
189
190 protected Set<String> initSequences(Connection connection, Dialect dialect)
191 throws SQLException {
192 Set<String> sequences = Sets.newHashSet();
193 if (dialect.supportsSequences()) {
194 String sql = dialect.getQuerySequencesString();
195 if (sql != null) {
196
197 Statement statement = null;
198 ResultSet rs = null;
199 try {
200 statement = connection.createStatement();
201 rs = statement.executeQuery(sql);
202
203 while (rs.next()) {
204 sequences.add(StringUtils.lowerCase(rs.getString(1))
205 .trim());
206 }
207 } finally {
208 rs.close();
209 statement.close();
210 }
211
212 }
213 }
214 return sequences;
215 }
216
217 protected void initTables(Connection conn) {
218 Map<String, PersistentClass> persistentClassMap = Maps.newHashMap();
219 for (PersistentClass persistentClass: metadata.getEntityBindings()) {
220
221 Table table = persistentClass.getTable();
222
223 log.debug( String.format("Entity: %s is mapped to table: %s",
224 persistentClass.getClassName(),
225 table.getName()));
226
227 String catalog = StringUtils.isBlank(table.getCatalog()) ? defaultCatalogName : table.getCatalog();
228 String schema = StringUtils.isBlank(table.getSchema()) ? defaultSchemaName : table.getSchema();
229 String qualifiedTableName = getQualifiedTableName(catalog, schema, table.getName()).render().toLowerCase();
230 persistentClassMap.put(qualifiedTableName, persistentClass);
231
232 if (log.isDebugEnabled()) {
233 for (Iterator propertyIterator = persistentClass.getPropertyIterator();
234 propertyIterator.hasNext(); ) {
235 Property property = (Property) propertyIterator.next();
236
237 for (Iterator columnIterator = property.getColumnIterator();
238 columnIterator.hasNext(); ) {
239 Column column = (Column) columnIterator.next();
240
241 log.debug(String.format("Property: %s is mapped on table column: %s of type: %s",
242 property.getName(),
243 column.getName(),
244 column.getSqlType())
245 );
246 }
247 }
248 }
249 }
250
251 try {
252 DatabaseMetaData jdbcMeta = conn.getMetaData();
253
254
255 for (DatabaseTableEnum table : DatabaseTableEnum.values()) {
256 String tableName = table.name().toLowerCase();
257 if (log.isDebugEnabled()) {
258 log.debug("Load metas of table: " + tableName);
259 }
260 String qualifiedTableName = getQualifiedTableName(defaultCatalogName, defaultSchemaName, tableName).render().toLowerCase();
261 PersistentClass persistentClass = persistentClassMap.get(qualifiedTableName);
262 entities.put(qualifiedTableName, persistentClass);
263
264 getTable(tableName, defaultSchemaName, defaultCatalogName, jdbcMeta, persistentClass);
265 }
266 }
267 catch (SQLException e) {
268 throw new BeanInitializationException(
269 "Could not init database meta on connection " + conn, e);
270 }
271 finally {
272 DataSourceUtils.releaseConnection(conn, dataSource);
273 }
274 }
275
276 protected SumarisTableMetadata getTable(QualifiedTableName qualifiedTableName,
277 DatabaseMetaData jdbcMeta,
278 PersistentClass persistentClass) throws HibernateException, SQLException {
279 Preconditions.checkNotNull(qualifiedTableName);
280 Preconditions.checkNotNull(jdbcMeta);
281
282 String fullTableName = qualifiedTableName.render().toLowerCase();
283 SumarisTableMetadata sumarisTableMetadata = tables.get(fullTableName);
284 if (sumarisTableMetadata == null) {
285
286 if (persistentClass == null) {
287 persistentClass = entities.get(fullTableName);
288 }
289
290
291 if (persistentClass != null) {
292
293 Table table = persistentClass.getTable();
294 table.setCatalog(qualifiedTableName.getCatalogName() != null ? qualifiedTableName.getCatalogName().getText() : null);
295 table.setSchema(qualifiedTableName.getSchemaName() != null ? qualifiedTableName.getSchemaName().getText() : null);
296 sumarisTableMetadata = new SumarisHibernateTableMetadata(table, this, jdbcMeta, persistentClass);
297 }
298
299
300 else {
301 sumarisTableMetadata = new SumarisTableMetadata(qualifiedTableName, this, jdbcMeta);
302 }
303
304
305
306 String tableName = qualifiedTableName.getTableName().getText().toLowerCase();
307 if (!tableName.startsWith("ext_") && !tableName.startsWith("agg_")) {
308 tables.put(fullTableName, sumarisTableMetadata);
309 }
310 }
311 return sumarisTableMetadata;
312 }
313
314 protected SumarisTableMetadata getTable(String name,
315 String schema,
316 String catalog,
317 DatabaseMetaData jdbcMeta,
318 PersistentClass persistentClass) throws HibernateException, SQLException {
319 return getTable(getQualifiedTableName(catalog, schema, name), jdbcMeta, persistentClass);
320 }
321
322 public SumarisTableMetadata getTable(String name,
323 String schema,
324 String catalog) throws HibernateException {
325 QualifiedTableName qualifiedTableName = getQualifiedTableName(catalog, schema, name);
326 SumarisTableMetadata sumarisTableMetadata = tables.get(qualifiedTableName.render().toLowerCase());
327 if (sumarisTableMetadata == null) {
328
329 Connection conn = null;
330 try {
331 conn = DataSourceUtils.getConnection(dataSource);
332 DatabaseMetaData jdbcMeta = conn.getMetaData();
333 return getTable(qualifiedTableName, jdbcMeta, null);
334 } catch (SQLException e) {
335 throw new RuntimeException(
336 "Could not init database meta on connection " + conn, e);
337 } finally {
338 DataSourceUtils.releaseConnection(conn, dataSource);
339 }
340
341 }
342 return sumarisTableMetadata;
343 }
344 }