View Javadoc
1   /*
2    * (c) Copyright 2006-2020 by rapiddweller GmbH & Volker Bergmann. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, is permitted under the terms of the
6    * GNU General Public License.
7    *
8    * For redistributing this software or a derivative work under a license other
9    * than the GPL-compatible Free Software License as defined by the Free
10   * Software Foundation or approved by OSI, you must first obtain a commercial
11   * license to this software product from rapiddweller GmbH & Volker Bergmann.
12   *
13   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14   * WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS,
15   * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
17   * HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24   * POSSIBILITY OF SUCH DAMAGE.
25   */
26  
27  package com.rapiddweller.platform.db;
28  
29  import com.rapiddweller.benerator.Consumer;
30  import com.rapiddweller.benerator.storage.AbstractStorageSystem;
31  import com.rapiddweller.benerator.storage.StorageSystemInserter;
32  import com.rapiddweller.common.CollectionUtil;
33  import com.rapiddweller.common.ConfigurationError;
34  import com.rapiddweller.common.ConnectFailedException;
35  import com.rapiddweller.common.Context;
36  import com.rapiddweller.common.IOUtil;
37  import com.rapiddweller.common.ImportFailedException;
38  import com.rapiddweller.common.ObjectNotFoundException;
39  import com.rapiddweller.common.StringUtil;
40  import com.rapiddweller.common.collection.OrderedNameMap;
41  import com.rapiddweller.common.converter.AnyConverter;
42  import com.rapiddweller.common.version.VersionNumber;
43  import com.rapiddweller.format.DataSource;
44  import com.rapiddweller.format.util.ConvertingDataSource;
45  import com.rapiddweller.jdbacl.ColumnInfo;
46  import com.rapiddweller.jdbacl.DBUtil;
47  import com.rapiddweller.jdbacl.DatabaseDialect;
48  import com.rapiddweller.jdbacl.DatabaseDialectManager;
49  import com.rapiddweller.jdbacl.JDBCConnectData;
50  import com.rapiddweller.jdbacl.ResultSetConverter;
51  import com.rapiddweller.jdbacl.dialect.OracleDialect;
52  import com.rapiddweller.jdbacl.model.DBCatalog;
53  import com.rapiddweller.jdbacl.model.DBColumn;
54  import com.rapiddweller.jdbacl.model.DBDataType;
55  import com.rapiddweller.jdbacl.model.DBForeignKeyConstraint;
56  import com.rapiddweller.jdbacl.model.DBMetaDataImporter;
57  import com.rapiddweller.jdbacl.model.DBPrimaryKeyConstraint;
58  import com.rapiddweller.jdbacl.model.DBSchema;
59  import com.rapiddweller.jdbacl.model.DBTable;
60  import com.rapiddweller.jdbacl.model.DBUniqueConstraint;
61  import com.rapiddweller.jdbacl.model.Database;
62  import com.rapiddweller.jdbacl.model.cache.CachingDBImporter;
63  import com.rapiddweller.jdbacl.model.jdbc.JDBCDBImporter;
64  import com.rapiddweller.jdbacl.model.jdbc.JDBCMetaDataUtil;
65  import com.rapiddweller.model.data.ComplexTypeDescriptor;
66  import com.rapiddweller.model.data.ComponentDescriptor;
67  import com.rapiddweller.model.data.DataModel;
68  import com.rapiddweller.model.data.Entity;
69  import com.rapiddweller.model.data.IdDescriptor;
70  import com.rapiddweller.model.data.Mode;
71  import com.rapiddweller.model.data.PartDescriptor;
72  import com.rapiddweller.model.data.ReferenceDescriptor;
73  import com.rapiddweller.model.data.SimpleTypeDescriptor;
74  import com.rapiddweller.model.data.TypeDescriptor;
75  import com.rapiddweller.model.data.TypeMapper;
76  import com.rapiddweller.script.PrimitiveType;
77  import com.rapiddweller.script.expression.ConstantExpression;
78  import org.apache.logging.log4j.LogManager;
79  import org.apache.logging.log4j.Logger;
80  
81  import java.io.File;
82  import java.math.BigDecimal;
83  import java.sql.Blob;
84  import java.sql.Connection;
85  import java.sql.DatabaseMetaData;
86  import java.sql.PreparedStatement;
87  import java.sql.ResultSet;
88  import java.sql.SQLException;
89  import java.sql.Types;
90  import java.util.ArrayList;
91  import java.util.Collection;
92  import java.util.HashMap;
93  import java.util.List;
94  import java.util.Map;
95  import java.util.concurrent.atomic.AtomicInteger;
96  
97  import static com.rapiddweller.jdbacl.SQLUtil.createCatSchTabString;
98  import static com.rapiddweller.jdbacl.SQLUtil.renderQuery;
99  
100 /**
101  * Abstract class that serves as parent for classes which connect to databases using JDBC.<br/><br/>
102  * Created: 07.01.2013 08:11:25
103  *
104  * @author Volker Bergmann
105  * @since 0.8.0
106  */
107 public abstract class DBSystem extends AbstractStorageSystem {
108 
109   private static final int DEFAULT_FETCH_SIZE = 100;
110   private static final VersionNumber MIN_ORACLE_VERSION =
111       VersionNumber.valueOf("10.2.0.4");
112   private static final TypeDescriptor[] EMPTY_TYPE_DESCRIPTOR_ARRAY =
113       new TypeDescriptor[0];
114 
115 
116   /**
117    * The Logger.
118    */
119   protected final Logger logger = LogManager.getLogger(getClass());
120   private final TypeMapper driverTypeMapper;
121   private final AtomicInteger invalidationCount;
122   /**
123    * The Batch.
124    */
125   protected boolean batch;
126   /**
127    * The Read only.
128    */
129   protected boolean readOnly;
130   /**
131    * The Database.
132    */
133   protected Database database;
134   /**
135    * The Importer.
136    */
137   protected DBMetaDataImporter importer;
138   /**
139    * The Tables.
140    */
141   protected Map<String, DBTable> tables;
142   /**
143    * The Dialect.
144    */
145   protected DatabaseDialect dialect;
146   private String id;
147   private String environment;
148   private String url;
149   private String user;
150   private String password;
151   private String driver;
152   private String catalogName;
153   private String schemaName;
154   private String includeTables;
155   private String excludeTables;
156   private boolean metaDataCache;
157   private boolean lazy;
158   private boolean acceptUnknownColumnTypes;
159   private int fetchSize;
160   private OrderedNameMap<TypeDescriptor> typeDescriptors;
161   private boolean dynamicQuerySupported;
162   private boolean connectedBefore;
163 
164   /**
165    * Instantiates a new Db system.
166    *
167    * @param id        the id
168    * @param url       the url
169    * @param driver    the driver
170    * @param user      the user
171    * @param password  the password
172    * @param dataModel the data model
173    */
174   public DBSystem(String id, String url, String driver, String user,
175                   String password, DataModel dataModel) {
176     this(id, dataModel);
177     setUrl(url);
178     setUser(user);
179     setPassword(password);
180     setDriver(driver);
181     checkOracleDriverVersion(driver);
182   }
183 
184   /**
185    * Instantiates a new Db system.
186    *
187    * @param id          the id
188    * @param environment the environment
189    * @param dataModel   the data model
190    */
191   public DBSystem(String id, String environment, DataModel dataModel) {
192     this(id, dataModel);
193     setEnvironment(environment);
194   }
195 
196   private DBSystem(String id, DataModel dataModel) {
197     setId(id);
198     setDataModel(dataModel);
199     setSchema(null);
200     setIncludeTables(".*");
201     setExcludeTables(null);
202     setFetchSize(DEFAULT_FETCH_SIZE);
203     setMetaDataCache(false);
204     setBatch(false);
205     setReadOnly(false);
206     setLazy(true);
207     setDynamicQuerySupported(true);
208     this.typeDescriptors = null;
209     this.driverTypeMapper = driverTypeMapper();
210     this.connectedBefore = false;
211     this.invalidationCount = new AtomicInteger();
212   }
213 
214 
215   private static String decimalGranularity(int scale) {
216     if (scale == 0) {
217       return "1";
218     }
219     return "0." + "0".repeat(Math.max(0, scale - 1)) +
220         1;
221   }
222 
223   private static TypeMapper driverTypeMapper() {
224     return new TypeMapper(
225         "byte", Byte.class,
226         "short", Short.class,
227         "int", Integer.class,
228         "big_integer", Long.class,
229         "float", Float.class,
230         "double", Double.class,
231         "big_decimal", BigDecimal.class,
232 
233         "boolean", Boolean.class,
234         "char", Character.class,
235         "date", java.sql.Date.class,
236         "timestamp", java.sql.Timestamp.class,
237 
238         "string", java.sql.Clob.class,
239         "string", String.class,
240 
241         "binary", Blob.class,
242         "binary", byte[].class
243 
244 
245     );
246   }
247 
248   @Override
249   public String getId() {
250     return id;
251   }
252 
253   /**
254    * Sets id.
255    *
256    * @param id the id
257    */
258   public void setId(String id) {
259     this.id = id;
260   }
261 
262   /**
263    * Gets environment.
264    *
265    * @return the environment
266    */
267   public String getEnvironment() {
268     return (environment != null ? environment : user);
269   }
270 
271   private void setEnvironment(String environment) {
272     if (StringUtil.isEmpty(environment)) {
273       this.environment = null;
274       return;
275     }
276     logger.debug("setting environment '{}'", environment);
277     JDBCConnectData connectData = DBUtil.getConnectData(environment);
278     this.environment = environment;
279     this.url = connectData.url;
280     this.driver = connectData.driver;
281     this.catalogName = connectData.catalog;
282     this.schemaName = connectData.schema;
283     this.user = connectData.user;
284     this.password = connectData.password;
285   }
286 
287   /**
288    * Gets driver.
289    *
290    * @return the driver
291    */
292   public String getDriver() {
293     return driver;
294   }
295 
296   /**
297    * Sets driver.
298    *
299    * @param driver the driver
300    */
301   public void setDriver(String driver) {
302     this.driver = driver;
303   }
304 
305   /**
306    * Gets url.
307    *
308    * @return the url
309    */
310   public String getUrl() {
311     return url;
312   }
313 
314   /**
315    * Sets url.
316    *
317    * @param url the url
318    */
319   public void setUrl(String url) {
320     this.url = url;
321   }
322 
323   /**
324    * Gets user.
325    *
326    * @return the user
327    */
328   public String getUser() {
329     return user;
330   }
331 
332   /**
333    * Sets user.
334    *
335    * @param user the user
336    */
337   public void setUser(String user) {
338     this.user = user;
339   }
340 
341   /**
342    * Gets password.
343    *
344    * @return the password
345    */
346   public String getPassword() {
347     return password;
348   }
349 
350   /**
351    * Sets password.
352    *
353    * @param password the password
354    */
355   public void setPassword(String password) {
356     this.password = StringUtil.emptyToNull(password);
357   }
358 
359   /**
360    * Gets catalog.
361    *
362    * @return the catalog
363    */
364   public String getCatalog() {
365     return catalogName;
366   }
367 
368   /**
369    * Sets catalog.
370    *
371    * @param catalog the catalog
372    */
373   public void setCatalog(String catalog) {
374     this.catalogName = catalog;
375   }
376 
377   /**
378    * Gets schema.
379    *
380    * @return the schema
381    */
382   public String getSchema() {
383     return schemaName;
384   }
385 
386   /**
387    * Sets schema.
388    *
389    * @param schema the schema
390    */
391   public void setSchema(String schema) {
392     this.schemaName = StringUtil.emptyToNull(StringUtil.trim(schema));
393   }
394 
395   /**
396    * Sets table filter.
397    *
398    * @param tableFilter the table filter
399    */
400   @Deprecated
401   public void setTableFilter(String tableFilter) {
402     setIncludeTables(tableFilter);
403   }
404 
405   /**
406    * Gets include tables.
407    *
408    * @return the include tables
409    */
410   public String getIncludeTables() {
411     return includeTables;
412   }
413 
414   /**
415    * Sets include tables.
416    *
417    * @param includeTables the include tables
418    */
419   public void setIncludeTables(String includeTables) {
420     this.includeTables = includeTables;
421   }
422 
423   /**
424    * Gets exclude tables.
425    *
426    * @return the exclude tables
427    */
428   public String getExcludeTables() {
429     return excludeTables;
430   }
431 
432   /**
433    * Sets exclude tables.
434    *
435    * @param excludeTables the exclude tables
436    */
437   public void setExcludeTables(String excludeTables) {
438     this.excludeTables = excludeTables;
439   }
440 
441   /**
442    * Is meta data cache boolean.
443    *
444    * @return the boolean
445    */
446   public boolean isMetaDataCache() {
447     return metaDataCache;
448   }
449 
450   /**
451    * Sets meta data cache.
452    *
453    * @param metaDataCache the meta data cache
454    */
455   public void setMetaDataCache(boolean metaDataCache) {
456     this.metaDataCache = metaDataCache;
457   }
458 
459   /**
460    * Is batch boolean.
461    *
462    * @return the boolean
463    */
464   public boolean isBatch() {
465     return batch;
466   }
467 
468   /**
469    * Sets batch.
470    *
471    * @param batch the batch
472    */
473   public void setBatch(boolean batch) {
474     this.batch = batch;
475   }
476 
477   /**
478    * Gets fetch size.
479    *
480    * @return the fetch size
481    */
482   public int getFetchSize() {
483     return fetchSize;
484   }
485 
486   /**
487    * Sets fetch size.
488    *
489    * @param fetchSize the fetch size
490    */
491   public void setFetchSize(int fetchSize) {
492     this.fetchSize = fetchSize;
493   }
494 
495   /**
496    * Is read only boolean.
497    *
498    * @return the boolean
499    */
500   public boolean isReadOnly() {
501     return readOnly;
502   }
503 
504   /**
505    * Sets read only.
506    *
507    * @param readOnly the read only
508    */
509   public void setReadOnly(boolean readOnly) {
510     this.readOnly = readOnly;
511   }
512 
513   /**
514    * Is lazy boolean.
515    *
516    * @return the boolean
517    */
518   public boolean isLazy() {
519     return lazy;
520   }
521 
522   /**
523    * Sets lazy.
524    *
525    * @param lazy the lazy
526    */
527   public void setLazy(boolean lazy) {
528     this.lazy = lazy;
529   }
530 
531 
532   // DescriptorProvider interface ------------------------------------------------------------------------------------
533 
534   /**
535    * Sets dynamic query supported.
536    *
537    * @param dynamicQuerySupported the dynamic query supported
538    */
539   public void setDynamicQuerySupported(boolean dynamicQuerySupported) {
540     this.dynamicQuerySupported = dynamicQuerySupported;
541   }
542 
543   /**
544    * Sets accept unknown column types.
545    *
546    * @param acceptUnknownColumnTypes the accept unknown column types
547    */
548   public void setAcceptUnknownColumnTypes(boolean acceptUnknownColumnTypes) {
549     this.acceptUnknownColumnTypes = acceptUnknownColumnTypes;
550   }
551 
552   // StorageSystem interface -----------------------------------------------------------------------------------------
553 
554   @Override
555   public TypeDescriptor[] getTypeDescriptors() {
556     logger.debug("getTypeDescriptors()");
557     parseMetadataIfNecessary();
558     if (typeDescriptors == null) {
559       return EMPTY_TYPE_DESCRIPTOR_ARRAY;
560     } else {
561       return CollectionUtil
562           .toArray(typeDescriptors.values(), TypeDescriptor.class);
563     }
564   }
565 
566   @Override
567   public TypeDescriptor getTypeDescriptor(String tableName) {
568     logger.debug("getTypeDescriptor({})", tableName);
569     parseMetadataIfNecessary();
570     return typeDescriptors.get(tableName);
571   }
572 
573   @Override
574   public void store(Entity entity) {
575     if (readOnly) {
576       throw new IllegalStateException(
577           "Tried to insert rows into table '" + entity.type() + "' " +
578               "though database '" + id + "' is read-only");
579     }
580     logger.debug("Storing {}", entity);
581     persistOrUpdate(entity, true);
582   }
583 
584   @Override
585   public void update(Entity entity) {
586     if (readOnly) {
587       throw new IllegalStateException(
588           "Tried to update table '" + entity.type() + "' " +
589               "though database '" + id + "' is read-only");
590     }
591     logger.debug("Updating {}", entity);
592     persistOrUpdate(entity, false);
593   }
594 
595   @Override
596   public void close() {
597     if (database != null) {
598       CachingDBImporter.updateCacheFile(database);
599     }
600     IOUtil.close(importer);
601   }
602 
603   /**
604    * Query entity by id entity.
605    *
606    * @param tableName the table name
607    * @param id        the id
608    * @return the entity
609    */
610   public Entity queryEntityById(String tableName, Object id) {
611     try {
612       logger.debug("queryEntityById({}, {})", tableName, id);
613       ComplexTypeDescriptor descriptor =
614           (ComplexTypeDescriptor) getTypeDescriptor(tableName);
615       PreparedStatement query = getSelectByPKStatement(descriptor);
616       query.setObject(1, id); // TODO v0.7.6 support composite keys
617       ResultSet resultSet = query.executeQuery();
618       if (resultSet.next()) {
619         return ResultSet2EntityConverter.convert(resultSet, descriptor);
620       } else {
621         return null;
622       }
623     } catch (SQLException e) {
624       throw new RuntimeException("Error querying " + tableName, e);
625     }
626   }
627 
628   @Override
629   @SuppressWarnings({"null", "checkstyle:VariableDeclarationUsageDistance"})
630   public DataSource<Entity> queryEntities(String type, String selector,
631                                           Context context) {
632     logger.debug("queryEntities({})", type);
633     Connection connection = getConnection();
634     boolean script = false;
635     if (selector != null && selector.startsWith("{") &&
636         selector.endsWith("}")) {
637       selector = selector.substring(1, selector.length() - 1);
638       script = true;
639     }
640     String sql;
641     if (StringUtil.isEmpty(selector)) {
642       sql = "select * from " +
643           createCatSchTabString(catalogName, schemaName, type,
644               dialect);
645     } else if (StringUtil.startsWithIgnoreCase(selector, "select") ||
646         StringUtil.startsWithIgnoreCase(selector, "'select")) {
647       sql = selector;
648     } else if (selector.startsWith("ftl:") || !script) {
649       sql = "select * from " +
650           createCatSchTabString(catalogName, schemaName, type,
651               dialect) + " WHERE " + selector;
652     } else {
653       sql = "'select * from " +
654           createCatSchTabString(catalogName, schemaName, type,
655               dialect) + " WHERE ' + " + selector;
656     }
657     if (script) {
658       sql = '{' + sql + '}';
659     }
660     DataSource<ResultSet> source = createQuery(sql, context, connection);
661     return new EntityResultSetDataSource(source,
662         (ComplexTypeDescriptor) getTypeDescriptor(type));
663   }
664 
665   /**
666    * Count entities long.
667    *
668    * @param tableName the table name
669    * @return the long
670    */
671   public long countEntities(String tableName) {
672     logger.debug("countEntities({})", tableName);
673     String query = "select count(*) from " +
674         createCatSchTabString(catalogName, schemaName, tableName,
675             dialect);
676     return DBUtil.queryLong(query, getConnection());
677   }
678 
679   @Override
680   public DataSource<?> queryEntityIds(String tableName, String selector,
681                                       Context context) {
682     logger.debug("queryEntityIds({}, {})", tableName, selector);
683 
684     // check for script
685     boolean script = false;
686     if (selector != null && selector.startsWith("{") &&
687         selector.endsWith("}")) {
688       selector = selector.substring(1, selector.length() - 1);
689       script = true;
690     }
691 
692     // find out pk columns
693     DBTable table = getTable(tableName);
694     String[] pkColumnNames = table.getPKColumnNames();
695     if (pkColumnNames.length == 0) {
696       throw new ConfigurationError(
697           "Cannot create reference to table " + tableName +
698               " since it does not define a primary key");
699     }
700 
701     // construct selector
702     String query =
703         renderQuery(catalogName, schemaName, tableName, pkColumnNames,
704             dialect);
705     if (selector != null) {
706       if (script) {
707         query = "{'" + query + " where ' + " + selector + "}";
708       } else {
709         query += " where " + selector;
710       }
711     }
712     return query(query, true, context);
713   }
714 
715   @Override
716   @SuppressWarnings({"unchecked", "rawtypes"})
717   public DataSource<?> query(String query, boolean simplify,
718                              Context context) {
719     logger.debug("query({})", query);
720     Connection connection = getConnection();
721     QueryDataSource resultSetIterable =
722         createQuery(query, context, connection);
723     ResultSetConverter converter =
724         new ResultSetConverter(Object.class, simplify);
725     return new ConvertingDataSource<ResultSet, Object>(resultSetIterable,
726         converter);
727   }
728 
729   // database-specific interface -------------------------------------------------------------------------------------
730 
731   /**
732    * Inserter consumer.
733    *
734    * @return the consumer
735    */
736   public Consumer inserter() {
737     return new StorageSystemInserter(this);
738   }
739 
740   /**
741    * Inserter consumer.
742    *
743    * @param tableName the table name
744    * @return the consumer
745    */
746   public Consumer inserter(String tableName) {
747     return new StorageSystemInserter(this,
748         (ComplexTypeDescriptor) getTypeDescriptor(tableName));
749   }
750 
751   /**
752    * Gets connection.
753    *
754    * @return the connection
755    */
756   public abstract Connection getConnection();
757 
758   /**
759    * Gets select by pk statement.
760    *
761    * @param descriptor the descriptor
762    * @return the select by pk statement
763    */
764   protected abstract PreparedStatement getSelectByPKStatement(
765       ComplexTypeDescriptor descriptor);
766 
767   /**
768    * Table exists boolean.
769    *
770    * @param tableName the table name
771    * @return the boolean
772    */
773   public boolean tableExists(String tableName) {
774     logger.debug("tableExists({})", tableName);
775     return (getTypeDescriptor(tableName) != null);
776   }
777 
778   /**
779    * Create sequence.
780    *
781    * @param name the name
782    * @throws SQLException the sql exception
783    */
784   public void createSequence(String name) throws SQLException {
785     getDialect().createSequence(name, 1, getConnection());
786   }
787 
788   /**
789    * Drop sequence.
790    *
791    * @param name the name
792    */
793   public void dropSequence(String name) {
794     execute(getDialect().renderDropSequence(name));
795   }
796 
797   @Override
798   public Object execute(String sql) {
799     try {
800       DBUtil.executeUpdate(sql, getConnection());
801       return null;
802     } catch (SQLException e) {
803       throw new RuntimeException(e);
804     }
805   }
806 
807   /**
808    * Next sequence value long.
809    *
810    * @param sequenceName the sequence name
811    * @return the long
812    */
813   public long nextSequenceValue(String sequenceName) {
814     return DBUtil
815         .queryLong(getDialect().renderFetchSequenceValue(sequenceName),
816             getConnection());
817   }
818 
819   /**
820    * Sets sequence value.
821    *
822    * @param sequenceName the sequence name
823    * @param value        the value
824    * @throws SQLException the sql exception
825    */
826   public void setSequenceValue(String sequenceName, long value)
827       throws SQLException {
828     getDialect().setNextSequenceValue(sequenceName, value, getConnection());
829   }
830 
831   /**
832    * Create connection connection.
833    *
834    * @return the connection
835    */
836   protected Connection createConnection() {
837     try {
838       Connection connection =
839           DBUtil.connect(url, driver, user, password, readOnly);
840       if (!connectedBefore) {
841         DBUtil.logMetaData(connection);
842         connectedBefore = true;
843       }
844       connection.setAutoCommit(false);
845       return connection;
846     } catch (ConnectFailedException e) {
847       throw new RuntimeException(
848           "Connecting the database failed. URL: " + url, e);
849     } catch (SQLException e) {
850       throw new ConfigurationError("Turning off auto-commit failed", e);
851     }
852   }
853 
854   /**
855    * Invalidate.
856    */
857   public void invalidate() {
858     typeDescriptors = null;
859     tables = null;
860     invalidationCount.incrementAndGet();
861     if (environment != null) {
862       File bufferFile = CachingDBImporter.getCacheFile(environment);
863       if (bufferFile.exists()) {
864         if (!bufferFile.delete() && metaDataCache) {
865           logger.error("Deleting " + bufferFile + " failed");
866           metaDataCache = false;
867         } else {
868           logger.info("Deleted meta data cache file: " + bufferFile);
869         }
870 
871       }
872     }
873   }
874 
875   /**
876    * Invalidation count int.
877    *
878    * @return the int
879    */
880   public int invalidationCount() {
881     return invalidationCount.get();
882   }
883 
884   /**
885    * Parse meta data.
886    */
887   public void parseMetaData() {
888     this.tables = new HashMap<>();
889     this.typeDescriptors = OrderedNameMap.createCaseIgnorantMap();
890     //this.tableColumnIndexes = new HashMap<String, Map<String, Integer>>();
891     getDialect(); // make sure dialect is initialized
892     database = getDbMetaData();
893     if (lazy) {
894       logger.info(
895           "Fetching table details and ordering tables by dependency");
896     } else {
897       logger.info("Ordering tables by dependency");
898     }
899     List<DBTable> tables = DBUtil.dependencyOrderedTables(database);
900     for (DBTable table : tables) {
901       parseTable(table);
902     }
903   }
904 
905   /**
906    * Gets dialect.
907    *
908    * @return the dialect
909    */
910   public DatabaseDialect getDialect() {
911     if (dialect == null) {
912       try {
913         DatabaseMetaData metaData = getConnection().getMetaData();
914         String productName = metaData.getDatabaseProductName();
915         VersionNumber productVersion = VersionNumber
916             .valueOf(metaData.getDatabaseMajorVersion() + "." +
917                 metaData.getDatabaseMinorVersion());
918         dialect = DatabaseDialectManager
919             .getDialectForProduct(productName, productVersion);
920       } catch (SQLException e) {
921         throw new ConfigurationError("Database meta data access failed",
922             e);
923       }
924     }
925     return dialect;
926   }
927 
928   // java.lang.Object overrides ------------------------------------------------------------------
929 
930   /**
931    * Gets system.
932    *
933    * @return the system
934    */
935   public String getSystem() {
936     return getDialect().getSystem();
937   }
938 
939   // private helpers ------------------------------------------------------------------------------
940 
941   /**
942    * Gets db meta data.
943    *
944    * @return the db meta data
945    */
946   public Database getDbMetaData() {
947     if (database == null) {
948       fetchDbMetaData();
949     }
950     return database;
951   }
952 
953   @Override
954   public String toString() {
955     return getClass().getSimpleName() + '[' + user + '@' + url + ']';
956   }
957 
958   private void checkOracleDriverVersion(String driver) {
959     if (driver != null && driver.contains("oracle")) {
960       Connection connection;
961       try {
962         connection = getConnection();
963         DatabaseMetaData metaData = connection.getMetaData();
964         VersionNumber driverVersion =
965             VersionNumber.valueOf(metaData.getDriverVersion());
966         if (driverVersion.compareTo(MIN_ORACLE_VERSION) < 0) {
967           logger.warn(
968               "Your Oracle driver has a bug in metadata support. Please update to 10.2.0.4 or newer. " +
969                   "You can use that driver for accessing an Oracle 9 server as well.");
970         }
971       } catch (SQLException e) {
972         throw new ConfigurationError(e);
973       } finally {
974         close();
975       }
976     }
977   }
978 
979   private void fetchDbMetaData() {
980     try {
981       importer = createJDBCImporter();
982       if (metaDataCache) {
983         importer = new CachingDBImporter((JDBCDBImporter) importer,
984             getEnvironment());
985       }
986       database = importer.importDatabase();
987     } catch (ConnectFailedException e) {
988       throw new ConfigurationError("Database not available. ", e);
989     } catch (ImportFailedException e) {
990       throw new ConfigurationError(
991           "Unexpected failure of database meta data import. ", e);
992     }
993   }
994 
995   private JDBCDBImporter createJDBCImporter() {
996     return JDBCMetaDataUtil
997         .getJDBCDBImporter(getConnection(), user, catalogName, schemaName,
998             true, false, false, false, includeTables,
999             excludeTables);
1000   }
1001 
1002   private QueryDataSource createQuery(String query, Context context,
1003                                       Connection connection) {
1004     return new QueryDataSource(connection, query, fetchSize,
1005         (dynamicQuerySupported ? context : null));
1006   }
1007 
1008   /**
1009    * Gets statement.
1010    *
1011    * @param descriptor  the descriptor
1012    * @param insert      the insert
1013    * @param columnInfos the column infos
1014    * @return the statement
1015    */
1016   protected abstract PreparedStatement getStatement(
1017       ComplexTypeDescriptor descriptor, boolean insert,
1018       List<ColumnInfo> columnInfos);
1019 
1020   private void parseTable(DBTable table) {
1021     logger.debug("Parsing table {}", table);
1022     String tableName = table.getName();
1023     tables.put(tableName.toUpperCase(), table);
1024     ComplexTypeDescriptor complexType;
1025     if (lazy) {
1026       complexType = new LazyTableComplexTypeDescriptor(table, this);
1027     } else {
1028       complexType = mapTableToComplexTypeDescriptor(table,
1029           new ComplexTypeDescriptor(tableName, this));
1030     }
1031     typeDescriptors.put(tableName, complexType);
1032   }
1033 
1034   /**
1035    * Map table to complex type descriptor complex type descriptor.
1036    *
1037    * @param table       the table
1038    * @param complexType the complex type
1039    * @return the complex type descriptor
1040    */
1041   public ComplexTypeDescriptor mapTableToComplexTypeDescriptor(DBTable table,
1042                                                                ComplexTypeDescriptor complexType) {
1043     // process primary keys
1044     DBPrimaryKeyConstraint pkConstraint = table.getPrimaryKeyConstraint();
1045     if (pkConstraint != null) {
1046       String[] pkColumnNames = pkConstraint.getColumnNames();
1047       if (pkColumnNames.length ==
1048           1) { // TODO v0.7.6 support composite primary keys
1049         String columnName = pkColumnNames[0];
1050         DBColumn column = table.getColumn(columnName);
1051         table.getColumn(columnName);
1052         String abstractType = JdbcMetaTypeMapper
1053             .abstractType(column.getType(),
1054                 acceptUnknownColumnTypes);
1055         IdDescriptor idDescriptor =
1056             new IdDescriptor(columnName, this, abstractType);
1057         complexType.setComponent(idDescriptor);
1058       }
1059     }
1060 
1061     // process foreign keys
1062     for (DBForeignKeyConstraint constraint : table
1063         .getForeignKeyConstraints()) {
1064       String[] foreignKeyColumnNames =
1065           constraint.getForeignKeyColumnNames();
1066       if (foreignKeyColumnNames.length == 1) {
1067         String fkColumnName = foreignKeyColumnNames[0];
1068         DBTable targetTable = constraint.getRefereeTable();
1069         DBColumn fkColumn =
1070             constraint.getTable().getColumn(fkColumnName);
1071         DBDataType concreteType = fkColumn.getType();
1072         String abstractType = JdbcMetaTypeMapper
1073             .abstractType(concreteType, acceptUnknownColumnTypes);
1074         ReferenceDescriptoror.html#ReferenceDescriptor">ReferenceDescriptor descriptor = new ReferenceDescriptor(
1075             fkColumnName,
1076             this,
1077             abstractType,
1078             targetTable.getName(),
1079             constraint.getRefereeColumnNames()[0]);
1080         descriptor.getLocalType(false).setSource(id);
1081         descriptor.setMinCount(new ConstantExpression<>(1L));
1082         descriptor.setMaxCount(new ConstantExpression<>(1L));
1083         boolean nullable = fkColumn.isNullable();
1084         descriptor.setNullable(nullable);
1085         complexType.setComponent(
1086             descriptor); // overwrite possible id descriptor for foreign keys
1087         if (logger.isDebugEnabled()) {
1088           logger.debug("Parsed reference " + table.getName() + '.' +
1089               descriptor);
1090         }
1091       } else {
1092         // TODO v0.7.6 handle composite keys
1093       }
1094     }
1095     // process normal columns
1096     for (DBColumn column : table.getColumns()) {
1097       try {
1098         logger.debug("parsing column: {}", column);
1099         String columnName = column.getName();
1100         if (complexType.getComponent(columnName) != null) {
1101           continue; // skip columns that were already parsed (fk)
1102         }
1103         String columnId = table.getName() + '.' + columnName;
1104         if (column.isVersionColumn()) {
1105           logger.debug("Leaving out version column {}", columnId);
1106           continue;
1107         }
1108         DBDataType columnType = column.getType();
1109         String type = JdbcMetaTypeMapper
1110             .abstractType(columnType, acceptUnknownColumnTypes);
1111         String defaultValue = column.getDefaultValue();
1112         SimpleTypeDescriptor typeDescriptor =
1113             new SimpleTypeDescriptor(columnId, this, type);
1114         if (defaultValue != null) {
1115           typeDescriptor.setDetailValue("constant", defaultValue);
1116         }
1117         if (column.getSize() != null) {
1118           typeDescriptor.setMaxLength(column.getSize());
1119         }
1120         if (column.getFractionDigits() != null) {
1121           if ("timestamp".equals(type)) {
1122             typeDescriptor.setGranularity("1970-01-02");
1123           } else {
1124             typeDescriptor.setGranularity(
1125                 decimalGranularity(column.getFractionDigits()));
1126           }
1127         }
1128         //typeDescriptors.put(typeDescriptor.getName(), typeDescriptor);
1129         PartDescriptor descriptor =
1130             new PartDescriptor(columnName, this);
1131         descriptor.setLocalType(typeDescriptor);
1132         descriptor.setMinCount(new ConstantExpression<>(1L));
1133         descriptor.setMaxCount(new ConstantExpression<>(1L));
1134         descriptor.setNullable(column.getNotNullConstraint() == null);
1135         List<DBUniqueConstraint> ukConstraints =
1136             column.getUkConstraints();
1137         for (DBUniqueConstraint constraint : ukConstraints) {
1138           if (constraint.getColumnNames().length == 1) {
1139             descriptor.setUnique(true);
1140           } else {
1141             logger.debug(
1142                 "Automated uniqueness assurance on multiple columns is not provided yet: " +
1143                     constraint);
1144             // TODO v0.7.6 support uniqueness constraints on combination of columns
1145           }
1146         }
1147         logger.debug(
1148             "parsed attribute " + columnId + ": " + descriptor);
1149         complexType.addComponent(descriptor);
1150       } catch (Exception e) {
1151         throw new ConfigurationError(
1152             "Error processing column " + column.getName() +
1153                 " of table " + table.getName(), e);
1154       }
1155     }
1156     return complexType;
1157   }
1158 
1159   /**
1160    * Gets write column infos.
1161    *
1162    * @param entity the entity
1163    * @param insert the insert
1164    * @return the write column infos
1165    */
1166   public List<ColumnInfo> getWriteColumnInfos(Entity entity, boolean insert) {
1167     String tableName = entity.type();
1168     DBTable table;
1169     if (entity.descriptor instanceof LazyTableComplexTypeDescriptor) {
1170       table = getTable(this.schemaName, tableName);
1171     } else {
1172       table = getTable(tableName);
1173     }
1174     List<String> pkColumnNames =
1175         CollectionUtil.toList(table.getPKColumnNames());
1176     ComplexTypeDescriptor typeDescriptor =
1177         (ComplexTypeDescriptor) getTypeDescriptor(tableName);
1178     Collection<ComponentDescriptor> componentDescriptors =
1179         typeDescriptor.getComponents();
1180     List<ColumnInfo> pkInfos = new ArrayList<>(componentDescriptors.size());
1181     List<ColumnInfo> normalInfos =
1182         new ArrayList<>(componentDescriptors.size());
1183     ComplexTypeDescriptor entityDescriptor = entity.descriptor();
1184     for (ComponentDescriptor dbCompDescriptor : componentDescriptors) {
1185       ComponentDescriptor enCompDescriptor =
1186           entityDescriptor.getComponent(dbCompDescriptor.getName());
1187       if (enCompDescriptor != null &&
1188           enCompDescriptor.getMode() == Mode.ignored) {
1189         continue;
1190       }
1191       if (dbCompDescriptor.getMode() != Mode.ignored) {
1192         String name = dbCompDescriptor.getName();
1193         SimpleTypeDescriptor type =
1194             (SimpleTypeDescriptor) dbCompDescriptor
1195                 .getTypeDescriptor();
1196         PrimitiveType primitiveType = type.getPrimitiveType();
1197         if (primitiveType == null) {
1198           if (!acceptUnknownColumnTypes) {
1199             throw new ConfigurationError(
1200                 "Column type of " + entityDescriptor.getName() +
1201                     "." +
1202                     dbCompDescriptor.getName() +
1203                     " unknown: " + type.getName());
1204           } else if (entity.get(type.getName()) instanceof String) {
1205             primitiveType = PrimitiveType.STRING;
1206           } else {
1207             primitiveType = PrimitiveType.OBJECT;
1208           }
1209         }
1210         String primitiveTypeName = primitiveType.getName();
1211         // TODO Version 1.2.0 wrong entity information when table with same name exists in different schema and is part of context.
1212         DBColumn column = table.getColumn(name);
1213         DBDataType columnType = column.getType();
1214         int sqlType = columnType.getJdbcType();
1215         Class<?> javaType =
1216             driverTypeMapper.concreteType(primitiveTypeName);
1217         ColumnInfo info = new ColumnInfo(name, sqlType, javaType);
1218         if (pkColumnNames.contains(name)) {
1219           pkInfos.add(info);
1220         } else {
1221           normalInfos.add(info);
1222         }
1223       }
1224     }
1225     if (insert) {
1226       pkInfos.addAll(normalInfos);
1227       return pkInfos;
1228     } else {
1229       normalInfos.addAll(pkInfos);
1230       return normalInfos;
1231     }
1232   }
1233 
1234   /**
1235    * Gets table.
1236    *
1237    * @param tableName the table name
1238    * @return the table
1239    */
1240   public DBTable getTable(String tableName) {
1241     parseMetadataIfNecessary();
1242     DBTable table = findTableInConfiguredCatalogAndSchema(schemaName, tableName);
1243     if (table != null) {
1244       return table;
1245     } else {
1246       table = findAnyTableOfName(tableName);
1247       if (table != null) {
1248         logger.warn("Table '" + tableName + "' not found " +
1249             "in the expected catalog or schema." +
1250             "I have taken it from catalog '" + table.getCatalog() +
1251             "' and schema '" + table.getSchema() + "' instead. " +
1252             "You better make sure this is right and fix the configuration");
1253         return table;
1254       }
1255     }
1256     throw new ObjectNotFoundException("Table " + tableName);
1257   }
1258 
1259   /**
1260    * Gets table.
1261    *
1262    * @param schemaName the schema name
1263    * @param tableName  the table name
1264    * @return the table
1265    */
1266   public DBTable getTable(String schemaName, String tableName) {
1267     parseMetadataIfNecessary();
1268     DBTable table = findTableInConfiguredCatalogAndSchema(schemaName, tableName);
1269     if (table != null) {
1270       return table;
1271     } else {
1272       table = findAnyTableOfName(tableName);
1273       if (table != null) {
1274         logger.info("Table '" + tableName + "' not found " +
1275             "in the expected catalog or schema." +
1276             "I have taken it from catalog '" + table.getCatalog() +
1277             "' and schema '" + table.getSchema() + "' instead. " +
1278             "You better make sure this is right and fix the configuration");
1279         return table;
1280       }
1281     }
1282     throw new ObjectNotFoundException("Table " + tableName);
1283   }
1284 
1285   private DBTable findAnyTableOfName(String tableName) {
1286     for (DBCatalog catalog : database.getCatalogs()) {
1287       for (DBSchema schema : catalog.getSchemas()) {
1288         DBTable table = schema.getTable(tableName);
1289         if (table != null) {
1290           return table;
1291         }
1292       }
1293     }
1294     return null;
1295   }
1296 
1297   private DBTable findTableInConfiguredCatalogAndSchema(String tableName) {
1298     DBCatalog catalog = database.getCatalog(catalogName);
1299     DBSchema dbSchema;
1300     if (catalog == null) {
1301       // logger.debug("no catalog set, try to get schema directly");
1302       return database.getCatalog("benerator").getSchema(schemaName).getTable(tableName);
1303     } else {
1304       dbSchema = catalog.getSchema(schemaName);
1305     }
1306     if (dbSchema != null) {
1307       return dbSchema.getTable(tableName);
1308     }
1309     return null;
1310   }
1311 
1312   private DBTable findTableInConfiguredCatalogAndSchema(String schemaName, String tableName) {
1313     DBSchema dbSchema;
1314     DBCatalog catalog = database.getCatalog(catalogName);
1315     if (catalog == null) {
1316       // logger.info("no catalog set, try to get schema directly");
1317       dbSchema = database.getSchema(schemaName);
1318     } else {
1319       dbSchema = catalog.getSchema(schemaName);
1320     }
1321     if (dbSchema != null) {
1322       return dbSchema.getTable(tableName);
1323     }
1324     return null;
1325   }
1326 
1327   private void persistOrUpdate(Entity entity, boolean insert) {
1328     parseMetadataIfNecessary();
1329     List<ColumnInfo> writeColumnInfos = getWriteColumnInfos(entity, insert);
1330     try {
1331       String tableName = entity.type();
1332       PreparedStatement statement =
1333           getStatement(entity.descriptor(), insert, writeColumnInfos);
1334       for (int i = 0; i < writeColumnInfos.size(); i++) {
1335         ColumnInfo info = writeColumnInfos.get(i);
1336         Object jdbcValue = entity.getComponent(info.name);
1337         if (info.type != null) {
1338           jdbcValue = AnyConverter.convert(jdbcValue, info.type);
1339         }
1340         try {
1341           boolean criticalOracleType =
1342               (dialect instanceof OracleDialect &&
1343                   (info.sqlType == Types.NCLOB ||
1344                       info.sqlType == Types.OTHER));
1345           if (jdbcValue != null ||
1346               criticalOracleType) { // Oracle is not able to perform setNull() on NCLOBs and NVARCHAR2
1347 
1348             statement.setObject(i + 1, jdbcValue);
1349           } else {
1350             statement.setNull(i + 1, info.sqlType);
1351           }
1352         } catch (SQLException e) {
1353           throw new RuntimeException(
1354               "error setting column " + tableName + '.' +
1355                   info.name, e);
1356         }
1357       }
1358       if (batch) {
1359         statement.addBatch();
1360       } else {
1361         int rowCount = statement.executeUpdate();
1362         if (rowCount == 0) {
1363           throw new RuntimeException(
1364               "Update failed because, since there is no database entry with the PK of " +
1365                   entity);
1366         }
1367       }
1368     } catch (Exception e) {
1369       throw new RuntimeException("Error in persisting " + entity, e);
1370     }
1371   }
1372 
1373   private void parseMetadataIfNecessary() {
1374     if (typeDescriptors == null) {
1375       parseMetaData();
1376     }
1377   }
1378 
1379 }