1   /*
2    * Copyright 2003 - 2013 The eFaps Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Revision:        $Rev$
17   * Last Changed:    $Date$
18   * Last Changed By: $Author$
19   */
20  
21  package org.efaps.db.databases;
22  
23  import java.sql.Connection;
24  import java.sql.DatabaseMetaData;
25  import java.sql.ResultSet;
26  import java.sql.SQLException;
27  import java.sql.Statement;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.UUID;
33  
34  import javax.naming.InitialContext;
35  import javax.naming.NamingException;
36  
37  import org.apache.commons.dbutils.RowProcessor;
38  import org.apache.commons.lang3.StringUtils;
39  import org.efaps.bpm.NamingStrategy;
40  import org.efaps.db.Context;
41  import org.efaps.db.databases.information.TableInformation;
42  import org.efaps.db.wrapper.SQLDelete;
43  import org.efaps.db.wrapper.SQLDelete.DeleteDefintion;
44  import org.efaps.db.wrapper.SQLInsert;
45  import org.efaps.db.wrapper.SQLPart;
46  import org.efaps.db.wrapper.SQLSelect;
47  import org.efaps.db.wrapper.SQLUpdate;
48  import org.efaps.init.INamingBinds;
49  import org.efaps.init.IeFapsProperties;
50  import org.efaps.update.util.InstallationException;
51  import org.efaps.util.EFapsException;
52  import org.efaps.util.cache.AbstractCache;
53  import org.efaps.util.cache.CacheReloadException;
54  import org.joda.time.ReadableDateTime;
55  import org.joda.time.format.ISODateTimeFormat;
56  import org.slf4j.Logger;
57  import org.slf4j.LoggerFactory;
58  /**
59   * Abstract definition of database specific information and methods like alter
60   * of table columns.
61   *
62   * @author The eFaps Team
63   * @version $Id$
64   * @param <T> derived DB class
65   */
66  public abstract class AbstractDatabase<T extends AbstractDatabase<?>>
67  {
68  
69      /**
70       * Pattern that  will be translated in a pattern that matches any sequence
71       * of zero or more characters.
72       */
73      public static final String WILDCARDPATTERN = "*";
74  
75      /**
76       * Pattern that  will be translated in a pattern that matches any any
77       * single character.
78       */
79      public static final String SINGLECHARACTERPATTERN = "?";
80  
81      /**
82       * Character used to escape the above patterns.
83       */
84      public static final String ESCAPECHARACTER = "\\";
85  
86      /**
87       * Pattern for the schema.
88       */
89      private static String SCHEMAPATTERN = null;
90  
91      /**
92       * Name of the catalog.
93       */
94      private static String CATALOG = null;
95  
96      /**
97       * Logging instance used in this class.
98       */
99      private static final Logger LOG = LoggerFactory.getLogger(AbstractDatabase.class);
100 
101     static {
102         try {
103             final InitialContext initCtx = new InitialContext();
104             javax.naming.Context envCtx = null;
105             try {
106                 envCtx = (javax.naming.Context) initCtx.lookup("java:comp/env");
107             } catch (final NamingException e) {
108                 AbstractDatabase.LOG.info("Catched NamingException on evaluation for DataBase.");
109             }
110             // for a build the context might be different, try this before surrender
111             if (envCtx == null) {
112                 envCtx = (javax.naming.Context) initCtx.lookup("java:/comp/env");
113             }
114             try {
115                 final Map<?, ?> props = (Map<?, ?>) envCtx.lookup(INamingBinds.RESOURCE_CONFIGPROPERTIES);
116                 if (props != null) {
117                     AbstractDatabase.SCHEMAPATTERN = (String) props.get(IeFapsProperties.DBSCHEMAPATTERN);
118                     AbstractDatabase.CATALOG = (String) props.get(IeFapsProperties.DBCATALOG);
119                 }
120             } catch (final NamingException e) {
121                 AbstractDatabase.LOG.info("Catched NamingException on evaluation for Properties.");
122             }
123         } catch (final NamingException e) {
124             AbstractDatabase.LOG.error("NamingException", e);
125         }
126     }
127 
128     /**
129      * The enumeration defines the known column types in the database.
130      */
131     public enum ColumnType
132     {
133         /** integer number. */
134         INTEGER,
135         /** numeric/decimal numbers. */
136         DECIMAL,
137         /** real number. */
138         REAL,
139         /** short string. */
140         STRING_SHORT,
141         /** long string. */
142         STRING_LONG,
143         /** date and time. */
144         DATETIME,
145         /** binary large object. */
146         BLOB,
147         /** character large object. */
148         CLOB,
149         /** boolean. */
150         BOOLEAN,
151     }
152 
153     /**
154      * The map stores the mapping between the column types used in eFaps and the
155      * database specific column types.
156      *
157      * @see #addMapping(ColumnType, String, String, String...)
158      * @see #getWriteSQLTypeName(ColumnType)
159      */
160     private final Map<AbstractDatabase.ColumnType, String> writeColTypeMap
161         = new HashMap<AbstractDatabase.ColumnType, String>();
162 
163     /**
164      * The map stores the mapping between column types used in the database and
165      * eFaps.
166      *
167      * @see #addMapping(ColumnType, String, String, String...)
168      * @see #getReadColumnTypes(String)
169      */
170     private final Map<String, Set<AbstractDatabase.ColumnType>> readColTypeMap
171         = new HashMap<String, Set<AbstractDatabase.ColumnType>>();
172 
173     /**
174      * The map stores the mapping between column types used in eFaps and the
175      * related null value select statement of the database.
176      *
177      * @see #addMapping(ColumnType, String, String, String...)
178      */
179     private final Map<AbstractDatabase.ColumnType, String> nullValueColTypeMap
180         = new HashMap<AbstractDatabase.ColumnType, String>();
181 
182     /**
183      * Caching for table information read from the SQL database.
184      *
185      * @see #getTableInformation(Connection, String)
186      */
187     private final TableInfoCache cache = new TableInfoCache();
188 
189     /**
190      * Method is used to determine if this DataBase is connected. It uses SQL
191      * statements against the database to determine if it is the right database
192      * using unique identifiers e.g. systemtables, version info etc.
193      *
194      * @param _connection Connection to be used foo analyze
195      * @return true if this database is connected
196      * @throws SQLException on error
197      */
198     public abstract boolean isConnected(final Connection _connection)
199         throws SQLException;
200 
201     /**
202      * Initializes the {@link #cache} for the table informations.
203      *
204      * @see #cache
205      */
206     public void initialize()
207     {
208         initialize(AbstractDatabase.class);
209     }
210 
211     /**
212      * Initializes the {@link #cache} for the table informations with given
213      * initializer.
214      *
215      * @param _class initializer class
216      * @see #cache
217      */
218     public void initialize(final Class<?> _class)
219     {
220         this.cache.initialize(_class);
221     }
222 
223     /**
224      * @return {@link #SCHEMAPATTERN}
225      */
226     protected String getSchemaPattern()
227     {
228         return AbstractDatabase.SCHEMAPATTERN;
229     }
230 
231     /**
232      * @return {@link #CATALOG}
233      */
234     protected String getCatalog()
235     {
236         return AbstractDatabase.CATALOG;
237     }
238 
239     /**
240      * Adds a new mapping for given eFaps column type used for mapping from and
241      * to the SQL database.
242      *
243      * @param _columnType column type within eFaps
244      * @param _writeTypeName SQL type name used to write (create new column
245      *            within a SQL table)
246      * @param _nullValueSelect null value select used within the query if a link
247      *            target could be a null (and so all selected values must null
248      *            in the SQL statement for objects without this link)
249      * @param _readTypeNames list of SQL type names returned from the database
250      *            meta data reading
251      * @see #readColTypeMap to map from an eFaps column type to a SQL type name
252      * @see #writeColTypeMap to map from a SQL type name to possible eFaps
253      *      column types
254      */
255     protected void addMapping(final ColumnType _columnType,
256                               final String _writeTypeName,
257                               final String _nullValueSelect,
258                               final String... _readTypeNames)
259     {
260         this.writeColTypeMap.put(_columnType, _writeTypeName);
261         this.nullValueColTypeMap.put(_columnType, _nullValueSelect);
262         for (final String readTypeName : _readTypeNames) {
263             Set<AbstractDatabase.ColumnType> colTypes = this.readColTypeMap.get(readTypeName);
264             if (colTypes == null) {
265                 colTypes = new HashSet<AbstractDatabase.ColumnType>();
266                 this.readColTypeMap.put(readTypeName, colTypes);
267             }
268             colTypes.add(_columnType);
269         }
270     }
271 
272     /**
273      *
274      * @param _tableName name of the table to insert
275      * @param _idCol column holding the id
276      * @param _newId <i>true</i> if a new id must be created; otherwise
277      *            <i>false</i>
278      * @return new SQL insert statement
279      */
280     public SQLInsert newInsert(final String _tableName,
281                                final String _idCol,
282                                final boolean _newId)
283     {
284         return new SQLInsert(_tableName, _idCol, _newId);
285     }
286 
287     /**
288      *
289      * @param _tableName name of the table to insert
290      * @param _idCol column holding the id
291      * @param _id id to update
292      * @return new SQL insert statement
293      */
294     public SQLUpdate newUpdate(final String _tableName,
295                                final String _idCol,
296                                final long _id)
297     {
298         return new SQLUpdate(_tableName, _idCol, _id);
299     }
300 
301     /**
302      * @param _definition deleteDefinitions
303      * @return new SQLDelete
304      */
305     public SQLDelete newDelete(final DeleteDefintion... _definition)
306     {
307         return new SQLDelete(_definition);
308     }
309 
310     /**
311      * @return new SQL select statement
312      */
313     public SQLSelect newSelect()
314     {
315         return new SQLSelect();
316     }
317 
318     /**
319      * Returns for given column type the database vendor specific type name.
320      *
321      * @param _columnType column type for which the vendor specific column type
322      *            should be returned
323      * @return SQL specific column type name
324      * @see #writeColTypeMap
325      * @see #addMapping used to define the map
326      */
327     protected String getWriteSQLTypeName(final ColumnType _columnType)
328     {
329         return this.writeColTypeMap.get(_columnType);
330     }
331 
332     /**
333      * Converts given SQL column type name in a set of eFaps column types. If no
334      * mapping is specified, a <code>null</code> is returned.
335      *
336      * @param _readTypeName SQL column type name read from the database
337      * @return set of eFaps column types (or <code>null</code> if not specified)
338      * @see #readColTypeMap
339      * @see #addMapping used to define the map
340      */
341     public Set<AbstractDatabase.ColumnType> getReadColumnTypes(final String _readTypeName)
342     {
343         return this.readColTypeMap.get(_readTypeName);
344     }
345 
346     /**
347      * Returns for given column type the database vendor specific null value
348      * select statement.
349      *
350      * @param _columnType column type for which the database vendor specific
351      *            null value select is searched
352      * @return null value select
353      * @see #nullValueColTypeMap
354      */
355     public String getNullValueSelect(final AbstractDatabase.ColumnType _columnType)
356     {
357         return this.nullValueColTypeMap.get(_columnType);
358     }
359 
360     /**
361      * The method returns the database vendor specific value for the current
362      * time stamp.
363      *
364      * @return vendor specific string of the current time stamp
365      */
366     public abstract String getCurrentTimeStamp();
367 
368     /**
369      * Get the vendor specific Timestamp cast implementation.
370      *
371      * @param _dateString dateTime that will be casted to an timestamp
372      * @return vendor specific implementation of timestamp
373      */
374     public abstract String getTimestampValue(final String _dateString);
375 
376     /**
377      * Method is used to generate the "dateString" used by the
378      * vendor specific Timestamp cast implementation.
379      * @see AbstractDatabase#getTimestampValue(String)
380      * @param _value    ReadableDateTime to be converted in a String
381      */
382     public String getStr4DateTime(final ReadableDateTime _value)
383     {
384         return _value.toDateTime().toString(ISODateTimeFormat.dateHourMinuteSecondFraction());
385     }
386 
387     /**
388      * Get the vendor specific Boolean cast implementation.
389      *
390      * @param _value boolean that will be casted to an number for oracle
391      * @return vendor specific implementation of boolean
392      */
393     public abstract Object getBooleanValue(final Boolean _value);
394 
395     /**
396      * The method implements a delete all of database user specific objects
397      * (e.g. tables, views etc...). The method is called before a complete
398      * rebuild is done.
399      *
400      * @param _con sql connection
401      * @throws SQLException if delete of the SQL data model failed
402      */
403     public abstract void deleteAll(final Connection _con)
404         throws SQLException;
405 
406     /**
407      * The method tests, if a view with given name exists.
408      *
409      * @param _con sql connection
410      * @param _viewName name of view to test
411      * @return <i>true</i> if view exists, otherwise <i>false</i>
412      * @throws SQLException if the exist check failed
413      */
414     public boolean existsView(final Connection _con,
415                               final String _viewName)
416         throws SQLException
417     {
418         boolean ret = false;
419 
420         final DatabaseMetaData metaData = _con.getMetaData();
421 
422         // first test with lower case
423         final ResultSet rs = metaData.getTables(null, null, _viewName.toLowerCase(), new String[] { "VIEW" });
424         if (rs.next()) {
425             ret = true;
426         }
427         rs.close();
428 
429         // then test with upper case
430         if (!ret) {
431             final ResultSet rsUC = metaData.getTables(null, null, _viewName.toUpperCase(), new String[] { "VIEW" });
432             if (rsUC.next()) {
433                 ret = true;
434             }
435             rsUC.close();
436         }
437 
438         return ret;
439     }
440 
441     /**
442      * Deletes given view <code>_name</code> in this database.
443      *
444      * @param _con SQL connection
445      * @param _name name of the sequence
446      * @return this instance
447      * @throws SQLException if delete of the sequence failed
448      */
449     public abstract T deleteView(final Connection _con,
450                                  final String _name)
451         throws SQLException;
452 
453     /**
454      * The method tests, if a view with given name exists.
455      *
456      * @param _con sql connection
457      * @param _tableName name of table to test
458      * @return <i>true</i> if SQL table exists, otherwise <i>false</i>
459      * @throws SQLException if the exist check for the table failed
460      */
461     public boolean existsTable(final Connection _con,
462                                final String _tableName)
463         throws SQLException
464     {
465         boolean ret = false;
466 
467         final DatabaseMetaData metaData = _con.getMetaData();
468 
469         // first test with lower case
470         final ResultSet rs = metaData.getTables(null, null, _tableName.toLowerCase(), new String[] { "TABLE" });
471         if (rs.next()) {
472             ret = true;
473         }
474         rs.close();
475 
476         // then test with upper case
477         if (!ret) {
478             final ResultSet rsUC = metaData.getTables(null, null, _tableName.toUpperCase(), new String[] { "TABLE" });
479             if (rsUC.next()) {
480                 ret = true;
481             }
482             rsUC.close();
483         }
484         return ret;
485     }
486 
487     /**
488      * Returns for given table name all information about the table and returns
489      * them as instance of {@link TableInformation}. The information is cached
490      * and NOT evaluated directly.
491      *
492      * @param _tableName name of SQL table for which the information is fetched
493      * @return instance of {@link TableInformation} with table information
494      * @throws SQLException if information about the table could not be fetched
495      * @see TableInformation
496      * @see #getRealTableInformation(Connection, String)
497      * @see #cache
498      */
499     public TableInformation getCachedTableInformation(final String _tableName)
500         throws SQLException
501     {
502         return this.cache.get(_tableName.toUpperCase());
503     }
504 
505     /**
506      * Evaluates for given table name all current information about the table
507      * and returns them as instance of {@link TableInformation}.
508      *
509      * @param _con SQL connection
510      * @param _tableName name of SQL table for which the information is fetched
511      * @return instance of {@link TableInformation} with table information
512      * @throws SQLException if information about the table could not be fetched
513      * @see TableInformation
514      * @see #getCachedTableInformation(String)
515      */
516     public TableInformation getRealTableInformation(final Connection _con,
517                                                     final String _tableName)
518         throws SQLException
519     {
520         final TableInformation tableInfo = new TableInformation(_tableName.toUpperCase());
521 
522         final Map<String, TableInformation> tableInfos = new HashMap<String, TableInformation>(1);
523         tableInfos.put(_tableName.toUpperCase(), tableInfo);
524         this.initTableInfoColumns(_con, null, tableInfos);
525         this.initTableInfoUniqueKeys(_con, null, tableInfos);
526         this.initTableInfoForeignKeys(_con, null, tableInfos);
527 
528         return tableInfo;
529     }
530 
531     /**
532      * A new SQL view <code>_view</code> is created. To create a correct view a
533      * dummy select on the value one is done (which will overwritten).
534      *
535      * @param _con SQL connection
536      * @param _view name of the view to create
537      * @return this instance
538      * @throws SQLException if the create of the table failed TODO: really
539      *             neeeded? not referenced anymore...
540      */
541     @SuppressWarnings("unchecked")
542     public T createView(final Connection _con,
543                         final String _view)
544         throws SQLException
545     {
546         final Statement stmt = _con.createStatement();
547         try {
548             stmt.executeUpdate(new StringBuilder().append("create view ").append(_view)
549                             .append(" as select 1").toString());
550         } finally {
551             stmt.close();
552         }
553         return (T) this;
554     }
555 
556     /**
557      * Method to create new sequence <code>_name</code>in this database. The
558      * next time the value for sequence <code>_name</code> will return
559      * <code>_value</code> (by calling {@link #nextSequence(Connection, String)}
560      * ).
561      *
562      * @param _con SQL connection
563      * @param _name name of the sequence
564      * @param _startValue start value for the sequence
565      * @return this instance
566      * @throws SQLException on error
567      */
568     public abstract T createSequence(final Connection _con,
569                                      final String _name,
570                                      final long _startValue)
571         throws SQLException;
572 
573     /**
574      * Deletes given sequence <code>_name</code> in this database.
575      *
576      * @param _con SQL connection
577      * @param _name name of the sequence
578      * @return this instance
579      * @throws SQLException if delete of the sequence failed
580      */
581     public abstract T deleteSequence(final Connection _con,
582                                      final String _name)
583         throws SQLException;
584 
585     /**
586      * Method to check for an existing Sequence in this Database.
587      *
588      * @param _con SQL connection
589      * @param _name name of the sequence
590      * @return true if exists, else false
591      * @throws SQLException on error
592      */
593     public abstract boolean existsSequence(final Connection _con,
594                                            final String _name)
595         throws SQLException;
596 
597     /**
598      * Method to get the next value from a given sequence in this database.
599      *
600      * @param _con SQL connection
601      * @param _name name of the sequence
602      * @return next value in sequence
603      * @throws SQLException on error
604      */
605     public abstract long nextSequence(final Connection _con,
606                                       final String _name)
607         throws SQLException;
608 
609     /**
610      * Method to define current value for sequence <code>_name</code>. The next
611      * time the value for sequence <code>_name</code> will return
612      * <code>_value</code> (by calling {@link #nextSequence(Connection, String)}
613      * ).
614      *
615      * @param _con SQL connection
616      * @param _name name of the sequence
617      * @param _value value for the sequence
618      * @return this instance
619      * @throws SQLException on error
620      */
621     public abstract T setSequence(final Connection _con,
622                                   final String _name,
623                                   final long _value)
624         throws SQLException;
625 
626     /**
627      * A new SQL table with unique column <code>ID</code> is created.
628      *
629      * @param _con SQL connection
630      * @param _table name of the table to create
631      * @return name of the table as it is insert into the database
632      * @throws SQLException if the create of the table failed
633      */
634     public abstract T createTable(final Connection _con,
635                                   final String _table)
636         throws SQLException;
637 
638     /**
639      * For a new created SQL table the column <code>ID</code> is update with a
640      * foreign key to a parent table.
641      *
642      * @param _con SQL connection
643      * @param _table name of the SQL table to update
644      * @param _parentTable name of the parent table
645      * @return this instance
646      * @throws InstallationException if the update of the table failed
647      */
648     public T defineTableParent(final Connection _con,
649                                final String _table,
650                                final String _parentTable)
651         throws InstallationException
652     {
653         return addForeignKey(_con, _table, _table + "_FK_ID", "ID", _parentTable + "(ID)", false);
654     }
655 
656     /**
657      * Defines a new created SQL table as auto incremented.
658      *
659      * @param _con SQL connection
660      * @param _table name of the SQL table to update
661      * @return this instance
662      * @throws SQLException if the update of the table failed
663      */
664     public abstract T defineTableAutoIncrement(final Connection _con,
665                                                final String _table)
666         throws SQLException;
667 
668     /**
669      * Adds a column to a SQL table.
670      *
671      * @param _con SQL connection
672      * @param _tableName name of table to update
673      * @param _columnName column to add
674      * @param _columnType type of column to add
675      * @param _defaultValue default value of the column (or null if not
676      *            specified)
677      * @param _length length of column to add (or 0 if not specified)
678      * @param _scale scale of the column (or 0 if not specified)
679      * @return this instance
680      * @throws SQLException if the column could not be added to the tables
681      */
682     // CHECKSTYLE:OFF
683     public T addTableColumn(final Connection _con,
684                             final String _tableName,
685                             final String _columnName,
686                             final ColumnType _columnType,
687                             final String _defaultValue,
688                             final int _length,
689                             final int _scale)
690         throws SQLException
691     {
692         // CHECKSTYLE:ON
693         final StringBuilder cmd = new StringBuilder();
694         cmd.append("alter table ").append(getTableQuote()).append(_tableName).append(getTableQuote()).append(' ')
695                         .append("add ").append(getColumnQuote()).append(_columnName).append(getColumnQuote())
696                         .append(' ')
697                         .append(getWriteSQLTypeName(_columnType));
698         if (_length > 0) {
699             cmd.append("(").append(_length);
700             if (_scale > 0) {
701                 cmd.append(",").append(_scale);
702             }
703             cmd.append(")");
704         }
705         if (_defaultValue != null) {
706             cmd.append(" default ").append(_defaultValue);
707         }
708 
709         AbstractDatabase.LOG.debug("    ..SQL> " + cmd.toString());
710 
711         final Statement stmt = _con.createStatement();
712         try {
713             stmt.execute(cmd.toString());
714         } finally {
715             stmt.close();
716         }
717 
718         @SuppressWarnings("unchecked")
719         final T ret = (T) this;
720         return ret;
721     }
722 
723     /**
724      * @param _con          SQL connection
725      * @param _tableName    name of table to update
726      * @param _columnName   column to update
727      * @param _isNotNull    actual isnull status
728      * @return this instance
729      * @throws SQLException if the column could not be added to the tables
730      */
731     public T updateColumnIsNotNull(final Connection _con,
732                                    final String _tableName,
733                                    final String _columnName,
734                                    final boolean _isNotNull)
735         throws SQLException
736     {
737         // to set not null it must be checked that there are no null values!
738         boolean executable = true;
739         if (_isNotNull) {
740             executable = !check4NullValues(_con, _tableName, _columnName);
741         }
742 
743         if (executable) {
744             final StringBuilder cmd = new StringBuilder();
745             cmd.append("alter table ").append(getTableQuote()).append(_tableName).append(getTableQuote())
746                 .append(getAlterColumnIsNotNull(_columnName, _isNotNull));
747 
748             AbstractDatabase.LOG.debug("    ..SQL> {}", cmd);
749 
750             // excecute statement
751             final Statement stmt = _con.createStatement();
752             try {
753                 stmt.execute(cmd.toString());
754             } finally {
755                 stmt.close();
756             }
757         } else {
758             AbstractDatabase.LOG.warn("Could not alter \"Not NUll\" on table '{}' column '{}'. "
759                             + "Perhaps the column contains null values?", _tableName, _columnName);
760         }
761         @SuppressWarnings("unchecked")
762         final T ret = (T) this;
763         return ret;
764     }
765 
766     /**
767      * @param _columnName column to update is not null.
768      * @param _isNotNull is the column not null
769      * @return sql snipplet
770      */
771     protected abstract StringBuilder getAlterColumnIsNotNull(final String _columnName,
772                                                              boolean _isNotNull);
773 
774     /**
775      * Check if a specific column contains null values.
776      * @param _con          SQL connection
777      * @param _tableName    name of table to check
778      * @param _columnName   column to check
779      * @return true if the column contains nulls, else false
780      * @throws SQLException on error
781      */
782     protected abstract boolean check4NullValues(final Connection _con,
783                                                 final String _tableName,
784                                                 final String _columnName)
785         throws SQLException;
786 
787     /**
788      * Adds a column to a SQL table.
789      *
790      * @param _con SQL connection
791      * @param _tableName name of table to update
792      * @param _columnName column to update
793      * @param _columnType type of column to update
794      * @param _length length of column to update (or 0 if not specified)
795      * @param _scale scale of the column (or 0 if not specified)
796      * @return this instance
797      * @throws SQLException if the column could not be added to the tables
798      */
799     public T updateColumn(final Connection _con,
800                           final String _tableName,
801                           final String _columnName,
802                           final ColumnType _columnType,
803                           final int _length,
804                           final int _scale)
805         throws SQLException
806     {
807         final StringBuilder cmd = new StringBuilder();
808         cmd.append("alter table ").append(getTableQuote()).append(_tableName).append(getTableQuote())
809             .append(getAlterColumn(_columnName, _columnType));
810         if (_length > 0) {
811             cmd.append("(").append(_length);
812             if (_scale > 0) {
813                 cmd.append(",").append(_scale);
814             }
815             cmd.append(")");
816         }
817         AbstractDatabase.LOG.debug("    ..SQL> " + cmd.toString());
818 
819         final Statement stmt = _con.createStatement();
820         try {
821             stmt.execute(cmd.toString());
822         } finally {
823             stmt.close();
824         }
825         @SuppressWarnings("unchecked")
826         final T ret = (T) this;
827         return ret;
828     }
829 
830     /**
831      * @param _columnName column to update
832      * @param _columnType type of column to update
833      * @return sql snipplet
834      */
835     protected abstract StringBuilder getAlterColumn(final String _columnName,
836                                                     final ColumnType _columnType);
837 
838     /**
839      * Adds a new unique key to given table name.
840      *
841      * @param _con SQL connection
842      * @param _tableName name of table for which the unique key must be created
843      * @param _uniqueKeyName name of unique key
844      * @param _columns comma separated list of column names for which the unique
845      *            key is created
846      * @return this instance
847      * @throws SQLException if the unique key could not be created
848      */
849     public T addUniqueKey(final Connection _con,
850                           final String _tableName,
851                           final String _uniqueKeyName,
852                           final String _columns)
853         throws SQLException
854     {
855         final StringBuilder cmd = new StringBuilder();
856         cmd.append("alter table ").append(_tableName).append(" ")
857                         .append("add constraint ").append(_uniqueKeyName).append(" ")
858                         .append("unique(").append(_columns).append(")");
859 
860         AbstractDatabase.LOG.debug("    ..SQL> " + cmd.toString());
861 
862         final Statement stmt = _con.createStatement();
863         try {
864             stmt.execute(cmd.toString());
865         } finally {
866             stmt.close();
867         }
868 
869         @SuppressWarnings("unchecked") final T ret = (T) this;
870         return ret;
871     }
872 
873     /**
874      * Adds a foreign key to given SQL table.
875      *
876      * @param _con SQL connection
877      * @param _tableName name of table for which the foreign key must be created
878      * @param _foreignKeyName name of foreign key to create
879      * @param _key key in the table (column name)
880      * @param _reference external reference (external table and column name)
881      * @param _cascade if the value in the external table is deleted, should
882      *            this value also automatically deleted?
883      * @return this instance
884      * @throws InstallationException if foreign key could not be defined for SQL
885      *             table
886      */
887     public T addForeignKey(final Connection _con,
888                            final String _tableName,
889                            final String _foreignKeyName,
890                            final String _key,
891                            final String _reference,
892                            final boolean _cascade)
893         throws InstallationException
894     {
895         final StringBuilder cmd = new StringBuilder()
896                         .append("alter table ").append(_tableName).append(" ")
897                         .append("add constraint ").append(_foreignKeyName).append(" ")
898                         .append("foreign key(").append(_key).append(") ")
899                         .append("references ").append(_reference);
900         if (_cascade) {
901             cmd.append(" on delete cascade");
902         }
903         AbstractDatabase.LOG.debug("    ..SQL> " + cmd.toString());
904 
905         try {
906             final Statement stmt = _con.createStatement();
907             try {
908                 stmt.execute(cmd.toString());
909             } finally {
910                 stmt.close();
911             }
912         } catch (final SQLException e) {
913             throw new InstallationException("Foreign key could not be created. SQL statement was:\n"
914                             + cmd.toString(), e);
915         }
916         @SuppressWarnings("unchecked")
917         final T ret = (T) this;
918         return ret;
919     }
920 
921     /**
922      * Adds a new check key to given SQL table.
923      *
924      * @param _con SQL connection
925      * @param _tableName name of the SQL table for which the check key must be
926      *            created
927      * @param _checkKeyName name of check key to create
928      * @param _condition condition of the check key
929      * @throws SQLException if check key could not be defined for SQL table
930      */
931     public void addCheckKey(final Connection _con,
932                             final String _tableName,
933                             final String _checkKeyName,
934                             final String _condition)
935         throws SQLException
936     {
937         final StringBuilder cmd = new StringBuilder()
938                         .append("alter table ").append(_tableName).append(" ")
939                         .append("add constraint ").append(_checkKeyName).append(" ")
940                         .append("check(").append(_condition).append(")");
941 
942         AbstractDatabase.LOG.debug("    ..SQL> " + cmd.toString());
943         // excecute statement
944         final Statement stmt = _con.createStatement();
945         try {
946             stmt.execute(cmd.toString());
947         } finally {
948             stmt.close();
949         }
950     }
951 
952     /**
953      * Returns the quote used to select tables.
954      *
955      * @return always empty string as default
956      */
957     public String getTableQuote()
958     {
959         return "";
960     }
961 
962     /**
963      * Returns the quote used to select columns.
964      *
965      * @return always empty string as default
966      */
967     public String getColumnQuote()
968     {
969         return "";
970     }
971 
972     /**
973      * @param _part Part the SQL is needed for
974      * @return String
975      */
976     public String getSQLPart(final SQLPart _part)
977     {
978         return _part.getDefaultValue();
979     }
980 
981     /**
982      * @param _value String value to be escaped
983      * @return escaped value in "'"
984      */
985     public String escapeForWhere(final String _value)
986     {
987         return "'" + StringUtils.replace(_value, "'", "''") + "'";
988     }
989 
990     /**
991      * @param _value value to be prepared for use in a match
992      * @return string prepared for match
993      */
994     public String prepare4Match(final String _value)
995     {
996         // Remove double escapes
997         String ret = StringUtils.replace(_value, "\\\\", "\\");
998         // escape '%' percent and '_' underscore
999         ret = StringUtils.replace(ret, "%", "\\%");
1000         ret = StringUtils.replace(ret, "_", "\\_");
1001         // replace any '*' that is not escaped by a '\'
1002         ret = ret.replaceAll("(?<!\\\\)\\" + AbstractDatabase.WILDCARDPATTERN, "%");
1003         // remove the escapecharacter from the '\*' and replace with a simple '*'
1004         ret = ret.replaceAll("(?>\\\\)\\" + AbstractDatabase.WILDCARDPATTERN, AbstractDatabase.WILDCARDPATTERN);
1005         // replace any '?' that is not escaped by a '_'
1006         ret = ret.replaceAll("(?<!\\\\)\\" + AbstractDatabase.SINGLECHARACTERPATTERN, "_");
1007         // remove the escapecharacter from the '\?' and replace with a simple '?'
1008         ret = ret.replaceAll("(?>\\\\)\\" + AbstractDatabase.SINGLECHARACTERPATTERN,
1009                         AbstractDatabase.SINGLECHARACTERPATTERN);
1010         return ret;
1011     }
1012 
1013     /**
1014      * <p>
1015      * This integer is used for the maximum numbers of Values inside an
1016      * expression.
1017      * </p>
1018      * <p>
1019      * The value is used in the OneRounQuery. The SQL statement looks like
1020      * "SELECT...WHERE..IN (val1,val2,val3,...valn)" The integer is the maximum
1021      * value for n before making a new Select.
1022      * </p>
1023      *
1024      * @return max Number of Value in an Expression, -1 if no max is known
1025      */
1026     public int getMaxExpressions()
1027     {
1028         return -1;
1029     }
1030 
1031     /**
1032      * A new id for given column of a SQL table is returned (e.g. with
1033      * sequences). This abstract class always throws a SQLException, because for
1034      * default, it is not needed to implement (only if the JDBC drive does not
1035      * implement method 'getGeneratedKeys' for java.sql.Statements).
1036      *
1037      * @param _con sql connection
1038      * @param _table sql table for which a new id must returned
1039      * @param _column sql table column for which a new id must returned
1040      * @return new id number
1041      * @throws SQLException always, because method itself is not implemented not
1042      *             not allowed to call
1043      */
1044     public long getNewId(final Connection _con,
1045                          final String _table,
1046                          final String _column)
1047         throws SQLException
1048     {
1049         throw new SQLException("method 'getNewId' not imlemented");
1050     }
1051 
1052     /**
1053      * The method returns if a database implementation supports to get generated
1054      * keys while inserting a new line in a SQL table.
1055      *
1056      * @return always <i>false</i> because not implemented in this class
1057      */
1058     public boolean supportsGetGeneratedKeys()
1059     {
1060         return false;
1061     }
1062 
1063     /**
1064      * The method returns if a database implementation support to get multiple
1065      * auto generated keys. If defined to <i>true</i>, the insert is done with
1066      * defined column names for the auto generated columns. Otherwise only
1067      * {@link java.sql.Statement#RETURN_GENERATED_KEYS} is given for the insert.
1068      *
1069      * @return always <i>false</i> because not implemented in this class
1070      * @see #supportsGetGeneratedKeys
1071      */
1072     public boolean supportsMultiGeneratedKeys()
1073     {
1074         return false;
1075     }
1076 
1077     /**
1078      * The method returns if a database implementation supports for blobs binary
1079      * input stream supports the available method or not.
1080      *
1081      * @return always <i>false</i> because not implemented in this class
1082      * @see #supportsBinaryInputStream
1083      */
1084     public boolean supportsBlobInputStreamAvailable()
1085     {
1086         return false;
1087     }
1088 
1089     /**
1090      * The method returns if a database implementation supports directly binary
1091      * stream for result sets (instead of using first blobs).
1092      *
1093      * @return always <i>false</i> because not implemented in this class
1094      * @see #supportsBlobInputStreamAvailable
1095      */
1096     public boolean supportsBinaryInputStream()
1097     {
1098         return false;
1099     }
1100 
1101     /**
1102      * Returns <i>true</i> if a database could handle big transactions used
1103      * within the eFaps updates.
1104      *
1105      * @return always <i>true</i> because normally a database should implement
1106      *         big transactions
1107      */
1108     public boolean supportsBigTransactions()
1109     {
1110         return true;
1111     }
1112 
1113     /**
1114      * @param _name name of the constraint as defined
1115      * @return name as used by the database
1116      * @throws EFapsException on error
1117      */
1118     public String getConstrainName(final String _name)
1119         throws EFapsException
1120     {
1121         return _name;
1122     }
1123 
1124     /**
1125      * @param _name name of the table as defined
1126      * @return name as used by the database
1127      * @throws EFapsException on error
1128      */
1129     public String getTableName(final String _name)
1130         throws EFapsException
1131     {
1132         return _name;
1133     }
1134 
1135     /**
1136      * Get the Dialect to access the DataBase using Hibernate.
1137      * @return the Dialect for Hibernate
1138      */
1139     public abstract String getHibernateDialect();
1140 
1141     /**
1142      * Instantiate the given DB class name and returns them.
1143      *
1144      * @param _dbClassName name of the class to instantiate
1145      * @return new database definition instance
1146      * @throws ClassNotFoundException if class for the DB is not found
1147      * @throws InstantiationException if DB class could not be instantiated
1148      * @throws IllegalAccessException if DB class could not be accessed
1149      */
1150     public static AbstractDatabase<?> findByClassName(final String _dbClassName)
1151         throws ClassNotFoundException, InstantiationException, IllegalAccessException
1152     {
1153         return (AbstractDatabase<?>) Class.forName(_dbClassName).newInstance();
1154     }
1155 
1156     /**
1157      * <p>
1158      * Fetches all table name for all tables and views. If a SQL statement is
1159      * given, this SQL statement is used instead of using the JDBC meta data
1160      * methods. The SQL select statement must define this column
1161      * <ul>
1162      * <li><b><code>TABLE_NAME</code></b> for the real name of the table.</li>
1163      * </ul>
1164      * </p>
1165      *
1166      * @param _con SQL connection
1167      * @param _sql SQL statement which must be executed if the JDBC
1168      *            functionality does not work (or null if JDBC meta data is used
1169      *            to fetch all tables and views)
1170      * @param _cache4Name map used to fetch depending on the table name the
1171      *            related table information
1172      * @throws SQLException if information could not be fetched from the data
1173      *             base
1174      */
1175     protected void initTableInfo(final Connection _con,
1176                                  final String _sql,
1177                                  final Map<String, TableInformation> _cache4Name)
1178         throws SQLException
1179     {
1180         Statement stmt = null;
1181         final ResultSet rs;
1182         if (_sql == null) {
1183             rs = _con.getMetaData().getTables(null, null, "%", new String[] { "TABLE", "VIEW" });
1184         } else        {
1185             stmt = _con.createStatement();
1186             rs = stmt.executeQuery(_sql);
1187         }
1188         try {
1189             while (rs.next()) {
1190                 final String tableName = rs.getString("TABLE_NAME").toUpperCase();
1191                 // ignore the tables managed by hibernate
1192                 if (!tableName.startsWith(NamingStrategy.HIBERNATEPREFIX.toUpperCase())
1193                                 && (tableName.startsWith("T_") || (tableName.startsWith("V_")))) {
1194                     _cache4Name.put(tableName, new TableInformation(tableName));
1195                 }
1196             }
1197         } finally {
1198             rs.close();
1199             if (stmt != null) {
1200                 stmt.close();
1201             }
1202         }
1203     }
1204 
1205     /**
1206      * <p>
1207      * Fetches all unique keys for all tables. If a SQL statement is given, this
1208      * SQL statement is used instead of using the JDBC meta data methods. The
1209      * SQL select statement must define this four columns
1210      * <ul>
1211      * <li><b><code>TABLE_NAME</code></b> for the real name of the table,</li>
1212      * <li><b><code>COLUMN_NAME</code></b> for the name of a column,</li>
1213      * <li><b><code>TYPE_NAME</code></b> for the name of the column type,</li>
1214      * <li><b><code>COLUMN_SIZE</code></b> for the size of the column,</li>
1215      * <li><b><code>DECIMAL_DIGITS</code></b> for the count of decimal digits
1216      * (if the <b><code>TYPE_NAME</code></b> is number) and</li>
1217      * <li><b><code>IS_NULLABLE</code></b> if the column could have no value
1218      * (with value &quot;NO&quot; if no null value is allowed).</li>
1219      * </ul>
1220      * </p>
1221      *
1222      * @param _con SQL connection
1223      * @param _sql SQL statement which must be executed if the JDBC
1224      *            functionality does not work (or null if JDBC meta data is used
1225      *            to fetch the table columns)
1226      * @param _cache4Name map used to cache depending on the table name the
1227      *            related table information
1228      * @throws SQLException if column information could not be fetched
1229      */
1230     protected void initTableInfoColumns(final Connection _con,
1231                                         final String _sql,
1232                                         final Map<String, TableInformation> _cache4Name)
1233         throws SQLException
1234     {
1235         Statement stmt = null;
1236         final ResultSet rs;
1237         if (_sql == null) {
1238             rs = _con.getMetaData().getColumns(null, null, "%", "%");
1239         } else        {
1240             stmt = _con.createStatement();
1241             rs = stmt.executeQuery(_sql);
1242         }
1243         try {
1244             while (rs.next()) {
1245                 final String tableName = rs.getString("TABLE_NAME").toUpperCase();
1246                 if (_cache4Name.containsKey(tableName)) {
1247                     final String colName = rs.getString("COLUMN_NAME").toUpperCase();
1248                     final String typeName = rs.getString("TYPE_NAME").toLowerCase();
1249                     final Set<AbstractDatabase.ColumnType> colTypes = AbstractDatabase.this
1250                                     .getReadColumnTypes(typeName);
1251                     if (colTypes == null) {
1252                         throw new SQLException("read unknown column type '" + typeName + "'");
1253                     }
1254                     final int size = rs.getInt("COLUMN_SIZE");
1255                     final int scale = rs.getInt("DECIMAL_DIGITS");
1256                     final boolean isNullable = !"NO".equalsIgnoreCase(rs.getString("IS_NULLABLE"));
1257                     _cache4Name.get(tableName).addColInfo(colName, colTypes, size, scale, isNullable);
1258                 }
1259             }
1260         } finally {
1261             rs.close();
1262             if (stmt != null) {
1263                 stmt.close();
1264             }
1265         }
1266     }
1267 
1268     /**
1269      * <p>
1270      * Fetches all unique keys for all tables. If a SQL statement is given, this
1271      * SQL statement is used instead of using the JDBC meta data methods. The
1272      * SQL select statement must define this four columns
1273      * <ul>
1274      * <li><b><code>TABLE_NAME</code></b> for the real name of the table,</li>
1275      * <li><b><code>INDEX_NAME</code></b> for the real name of the unique key
1276      * name,</li>
1277      * <li><b><code>COLUMN_NAME</code></b> for the name of a column within the
1278      * unique key and</li>
1279      * <li><b><code>ORDINAL_POSITION</code></b> for the position of the column
1280      * name within the unique key.</li>
1281      * </ul>
1282      * If more than one column is used to define the unique key, one line for
1283      * each column name with same index name must be used.
1284      * </p>
1285      *
1286      * @param _con SQL connection
1287      * @param _sql SQL statement which must be executed if the JDBC
1288      *            functionality does not work (or null if JDBC meta data is used
1289      *            to fetch the unique keys)
1290      * @param _cache4Name map used to fetch depending on the table name the
1291      *            related table information
1292      * @throws SQLException if unique keys could not be fetched
1293      */
1294     protected void initTableInfoUniqueKeys(final Connection _con,
1295                                            final String _sql,
1296                                            final Map<String, TableInformation> _cache4Name)
1297         throws SQLException
1298     {
1299         Statement stmt = null;
1300         final ResultSet rs;
1301         if (_sql == null) {
1302             rs = _con.getMetaData().getIndexInfo(null, null, "%", true, false);
1303         } else        {
1304             stmt = _con.createStatement();
1305             rs = stmt.executeQuery(_sql);
1306         }
1307         try {
1308             while (rs.next()) {
1309                 final String tableName = rs.getString("TABLE_NAME").toUpperCase();
1310                 if (_cache4Name.containsKey(tableName)) {
1311                     final String ukName = rs.getString("INDEX_NAME").toUpperCase();
1312                     final String colName = rs.getString("COLUMN_NAME").toUpperCase();
1313                     final int colIdx = rs.getInt("ORDINAL_POSITION");
1314                     _cache4Name.get(tableName).addUniqueKeyColumn(ukName, colIdx, colName);
1315                 }
1316             }
1317         } finally {
1318             rs.close();
1319             if (stmt != null) {
1320                 stmt.close();
1321             }
1322         }
1323     }
1324 
1325     /**
1326      * <p>
1327      * Fetches all foreign keys for all tables. If a SQL statement is given,
1328      * this SQL statement is used instead of using the JDBC meta data methods.
1329      * The SQL select statement must define this six columns
1330      * <ul>
1331      * <li><b><code>TABLE_NAME</code></b> for the real name of the table,</li>
1332      * <li><b><code>FK_NAME</code></b> for the real name of the foreign key
1333      * name,</li>
1334      * <li><b><code>FKCOLUMN_NAME</code></b> for the name of the column for
1335      * which the foreign key is defined,</li>
1336      * <li><b><code>PKTABLE_NAME</code></b> for the name of the referenced
1337      * table,</li>
1338      * <li><b><code>PKCOLUMN_NAME</code></b> for the name of column within the
1339      * referenced table and</li>
1340      * <li><b><code>DELETE_RULE</code></b> defining the rule what happens in the
1341      * case a row of the table is deleted (with value
1342      * {@link DatabaseMetaData#importedKeyCascade} in the case the delete is
1343      * cascaded).</li>
1344      * </ul>
1345      * </p>
1346      *
1347      * @param _con SQL connection
1348      * @param _sql SQL statement which must be executed if the JDBC
1349      *            functionality does not work (or null if JDBC meta data is used
1350      *            to fetch the foreign keys)
1351      * @param _cache4Name map used to fetch depending on the table name the
1352      *            related table information
1353      * @throws SQLException if foreign keys could not be fetched
1354      */
1355     protected void initTableInfoForeignKeys(final Connection _con,
1356                                             final String _sql,
1357                                             final Map<String, TableInformation> _cache4Name)
1358         throws SQLException
1359     {
1360         Statement stmt = null;
1361         final ResultSet rs;
1362         if (_sql == null) {
1363             rs = _con.getMetaData().getImportedKeys(null, null, "%");
1364         } else        {
1365             stmt = _con.createStatement();
1366             rs = stmt.executeQuery(_sql);
1367         }
1368         try {
1369             while (rs.next()) {
1370                 final String tableName = rs.getString("TABLE_NAME").toUpperCase();
1371                 if (_cache4Name.containsKey(tableName)) {
1372                     final String fkName = rs.getString("FK_NAME").toUpperCase();
1373                     final String colName = rs.getString("FKCOLUMN_NAME").toUpperCase();
1374                     final String refTableName = rs.getString("PKTABLE_NAME").toUpperCase();
1375                     final String refColName = rs.getString("PKCOLUMN_NAME").toUpperCase();
1376                     final boolean cascade = rs.getInt("DELETE_RULE") == DatabaseMetaData.importedKeyCascade;
1377                     _cache4Name.get(tableName).addForeignKey(fkName, colName, refTableName, refColName, cascade);
1378                 }
1379             }
1380         } finally {
1381             rs.close();
1382             if (stmt != null) {
1383                 stmt.close();
1384             }
1385         }
1386     }
1387 
1388     /**
1389      * Get the RowProcessor responsible to read the data from a recordset into
1390      * eFaps.
1391      *
1392      * @return a RowProcessor
1393      */
1394     public abstract RowProcessor getRowProcessor();
1395 
1396     /**
1397      * Implements the cache for the table information.
1398      *
1399      * @see AbstractDatabase#cache
1400      * @see TableInformation
1401      */
1402     private class TableInfoCache
1403         extends AbstractCache<TableInformation>
1404     {
1405 
1406         /**
1407          * {@inheritDoc}
1408          */
1409         @Override
1410         protected void readCache(final Map<Long, TableInformation> _cache4Id,
1411                                  final Map<String, TableInformation> _cache4Name,
1412                                  final Map<UUID, TableInformation> _cache4UUID)
1413             throws CacheReloadException
1414         {
1415             try {
1416                 final Connection con = Context.getThreadContext().getConnectionResource().getConnection();
1417                 AbstractDatabase.this.initTableInfo(con, null, _cache4Name);
1418                 AbstractDatabase.this.initTableInfoColumns(con, null, _cache4Name);
1419                 AbstractDatabase.this.initTableInfoUniqueKeys(con, null, _cache4Name);
1420                 AbstractDatabase.this.initTableInfoForeignKeys(con, null, _cache4Name);
1421             } catch (final SQLException e) {
1422                 throw new CacheReloadException("cache for table information could not be read", e);
1423             } catch (final EFapsException e) {
1424                 throw new CacheReloadException("cache for table information could not be read", e);
1425             }
1426         }
1427     }
1428 }