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 "NO" 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 }