001package fr.ifremer.adagio.synchro.meta; 002 003/* 004 * #%L 005 * Tutti :: Persistence 006 * $Id: ReferentialSynchroTableMetadata.java 1573 2014-02-04 16:41:40Z tchemit $ 007 * $HeadURL: http://svn.forge.codelutin.com/svn/tutti/trunk/tutti-persistence/src/main/java/fr/ifremer/adagio/core/service/technical/synchro/ReferentialSynchroTableMetadata.java $ 008 * %% 009 * Copyright (C) 2012 - 2014 Ifremer 010 * %% 011 * This program is free software: you can redistribute it and/or modify 012 * it under the terms of the GNU Affero General Public License as published by 013 * the Free Software Foundation, either version 3 of the License, or 014 * (at your option) any later version. 015 * 016 * This program is distributed in the hope that it will be useful, 017 * but WITHOUT ANY WARRANTY; without even the implied warranty of 018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 019 * GNU General Public License for more details. 020 * 021 * You should have received a copy of the GNU Affero General Public License 022 * along with this program. If not, see <http://www.gnu.org/licenses/>. 023 * #L% 024 */ 025 026import static org.nuiton.i18n.I18n.t; 027 028import java.lang.reflect.Field; 029import java.math.BigDecimal; 030import java.sql.ResultSet; 031import java.sql.SQLException; 032import java.sql.Types; 033import java.util.Date; 034import java.util.List; 035import java.util.Map; 036import java.util.Set; 037 038import oracle.sql.TIMESTAMP; 039 040import org.apache.commons.collections.CollectionUtils; 041import org.apache.commons.lang3.StringUtils; 042import org.apache.commons.logging.Log; 043import org.apache.commons.logging.LogFactory; 044import org.hibernate.dialect.Dialect; 045import org.hibernate.internal.util.StringHelper; 046import org.hibernate.mapping.ForeignKey; 047import org.hibernate.tool.hbm2ddl.ColumnMetadata; 048import org.hibernate.tool.hbm2ddl.ForeignKeyMetadata; 049import org.hibernate.tool.hbm2ddl.IndexMetadata; 050import org.hibernate.tool.hbm2ddl.TableMetadata; 051 052import com.google.common.base.Preconditions; 053import com.google.common.base.Predicate; 054import com.google.common.collect.ImmutableSet; 055import com.google.common.collect.Lists; 056import com.google.common.collect.Maps; 057import com.google.common.collect.Sets; 058 059import fr.ifremer.adagio.synchro.SynchroTechnicalException; 060import fr.ifremer.adagio.synchro.config.SynchroConfiguration; 061import fr.ifremer.adagio.synchro.dao.DaoUtils; 062import fr.ifremer.adagio.synchro.intercept.SynchroInterceptor; 063import fr.ifremer.adagio.synchro.service.SynchroContext; 064 065/** 066 * Overrides of the {@link TableMetadata} with some improvements: 067 * <ul> 068 * <li>Obtains number of columns via {@link #getColumnsCount()}</li> 069 * <li>Obtains all columns names available via {@link #getColumnNames()}</li> 070 * <li>Obtains primary key column names via {@link #getPkNames()}</li> 071 * </ul> 072 * <p/> 073 * And others methods used to synchronize referentials: 074 * <ul> 075 * <li>Obtains query to update a row of the table (column names order is the one introduced by method 076 * {@link #getColumnNames()}: {@link #getUpdateQuery()}</li> 077 * <li>Obtains query to insert a row in the table (column names order is the one introduced by method 078 * {@link #getColumnNames()}: {@link #getInsertQuery()}</li> 079 * </ul> 080 * Created on 1/14/14. 081 * 082 * @author Tony Chemit <chemit@codelutin.com> 083 * @since 3.0 084 */ 085public class SynchroTableMetadata { 086 private static final Log log = LogFactory.getLog(SynchroTableMetadata.class); 087 088 public enum TableInsertStrategy { 089 GENERATE_ID_FIRST, 090 INLINE_INSERT 091 }; 092 093 public static final String PK_SEPARATOR = "~~"; 094 095 public static final String COLUMN_SYNCHRONIZATION_STATUS = "synchronization_status"; 096 097 public static final String COLUMN_ID = "id"; 098 099 public static final String COLUMN_REMOTE_ID = "remote_id"; 100 101 public static final String COLUMN_UPDATE_DATE = "update_date"; 102 103 public static final String SEQUENCE_SUFFIX = "_seq"; 104 105 public static Set<String> PROTECTED_COLUMN_NAMES = ImmutableSet.<String> builder().add( 106 SynchroTableMetadata.COLUMN_REMOTE_ID, 107 SynchroTableMetadata.COLUMN_SYNCHRONIZATION_STATUS 108 ).build(); 109 110 private static Field delegateTableMetadataColumnsField = null; 111 112 @SuppressWarnings({ "unchecked", "rawtypes" }) 113 public static Map<String, ColumnMetadata> getColumns(TableMetadata delegate) { 114 try { 115 if (delegateTableMetadataColumnsField == null) { 116 delegateTableMetadataColumnsField = TableMetadata.class.getDeclaredField("columns"); 117 delegateTableMetadataColumnsField.setAccessible(true); 118 } 119 return Maps.<String, ColumnMetadata> newLinkedHashMap((Map) delegateTableMetadataColumnsField.get(delegate)); 120 } catch (Exception e) { 121 throw new SynchroTechnicalException(e.getMessage(), e); 122 } 123 } 124 125 protected final String selectPrimaryKeysAsStringQuery; 126 127 protected final String selectPrimaryKeysQuery; 128 129 protected final String selectMaxUpdateDateQuery; 130 131 protected final String countQuery; 132 133 protected final TableMetadata delegate; 134 135 protected final Map<String, SynchroColumnMetadata> columns; 136 137 protected List<SynchroJoinMetadata> childJoins; 138 139 protected List<SynchroJoinMetadata> parentJoins; 140 141 protected List<SynchroJoinMetadata> joins; 142 143 protected boolean hasJoins; 144 145 protected boolean hasChildJoins; 146 147 protected final List<String> protectedColumnNames; 148 149 protected final List<String> columnNames; 150 151 protected final Set<String> pkNames; 152 153 protected final int[] pkIndexs; 154 155 protected String insertQuery; 156 157 protected String insertWithGeneratedIdQuery; 158 159 protected String updateQuery; 160 161 protected final boolean withUpdateDateColumn; 162 163 protected final boolean withSynchronizationStatusColumn; 164 165 protected final boolean withIdColumn; 166 167 protected final boolean withRemoteIdColumn; 168 169 protected boolean isRoot; 170 171 protected final String countDataToUpdateQuery; 172 173 protected final String selectDataToUpdateQuery; 174 175 protected final String sequenceName; 176 177 protected final String selectSequenceNextValueString; 178 179 protected final String sequenceNextValString; 180 181 protected final String selectAllQuery; 182 183 protected final String selectDataQueryFromPk; 184 185 protected final Map<String, String> selectDataFromFkQueries; 186 187 protected final Map<String, String> selectDataFromFkQueriesBigParams; 188 189 protected final String selectIdFromRemoteIdQuery; 190 191 protected final String selectRemoteIdsQuery; 192 193 protected List<SynchroInterceptor> interceptors; 194 195 protected final SynchroContext context; 196 197 protected TableInsertStrategy insertStrategy; 198 199 protected final SynchroDatabaseMetadata dbMeta; 200 201 protected SynchroTableMetadata( 202 SynchroDatabaseMetadata dbMeta, 203 TableMetadata delegate, 204 List<SynchroInterceptor> interceptors, 205 String tableName, 206 Set<String> availableSequences, 207 Predicate<SynchroColumnMetadata> columnFilter) { 208 209 Preconditions.checkNotNull(delegate); 210 Preconditions.checkNotNull(dbMeta); 211 212 this.delegate = delegate; 213 this.interceptors = interceptors != null ? interceptors : Lists.<SynchroInterceptor> newArrayList(); 214 this.dbMeta = dbMeta; 215 this.context = dbMeta.getContext(); 216 217 try { 218 this.columns = initColumns(tableName, dbMeta, columnFilter); 219 this.columnNames = initColumnNames(columns); 220 this.protectedColumnNames = initProtectedColumnNames(columns); 221 this.pkNames = initPrimaryKeys(dbMeta); 222 Preconditions.checkNotNull(pkNames); 223 this.joins = Lists.newArrayList(); 224 this.hasJoins = !joins.isEmpty(); 225 this.withUpdateDateColumn = columnNames.contains(COLUMN_UPDATE_DATE); 226 this.withSynchronizationStatusColumn = protectedColumnNames.contains(COLUMN_SYNCHRONIZATION_STATUS); 227 this.withIdColumn = columnNames.contains(COLUMN_ID); 228 this.withRemoteIdColumn = protectedColumnNames.contains(COLUMN_REMOTE_ID); 229 this.sequenceName = initSequenceName(availableSequences); 230 Preconditions 231 .checkArgument(!withRemoteIdColumn || sequenceName != null, 232 String.format("Columns %s and %s found on table %s, but unable to retrieve a sequence !", COLUMN_REMOTE_ID, COLUMN_ID, 233 tableName)); 234 Preconditions.checkArgument(!withRemoteIdColumn || (pkNames.size() == 1 && withIdColumn), 235 String.format("Columns %s found on table %s, but more than one PK columns exists. Only %s column must be a PK.", 236 COLUMN_REMOTE_ID, tableName, COLUMN_ID)); 237 238 // Default values (could be override using interceptor) 239 this.isRoot = false; 240 } catch (Exception e) { 241 throw new SynchroTechnicalException(t("adagio.persistence.tableMetadata.instanciation.error", this), e); 242 } 243 244 this.pkIndexs = createPkIndex(); 245 this.selectSequenceNextValueString = createSelectSequenceNextValString(dbMeta.getDialect()); 246 this.sequenceNextValString = createSequenceNextValString(dbMeta.getDialect()); 247 this.insertQuery = createInsertQuery(); 248 this.insertWithGeneratedIdQuery = createInsertWithGeneratedIdQuery(); 249 this.updateQuery = createUpdateQuery(); 250 this.selectMaxUpdateDateQuery = createSelectMaxUpdateDateQuery(); 251 this.selectPrimaryKeysAsStringQuery = createSelectPrimaryKeysAsStringQuery(); 252 this.selectPrimaryKeysQuery = createSelectPrimaryKeysQuery(); 253 this.selectRemoteIdsQuery = createSelectRemoteIdsQuery(); 254 this.selectAllQuery = createSelectAllQuery(); 255 this.selectDataQueryFromPk = createSelectDataFromPkQuery(); 256 this.selectDataToUpdateQuery = createSelectDataToUpdateQuery(); 257 this.selectDataFromFkQueries = createSelectDataFromFkQueries(); 258 this.selectDataFromFkQueriesBigParams = createSelectDataFromFkQueriesBigParams(dbMeta); 259 this.selectIdFromRemoteIdQuery = createSelectIdFromRemoteIdQuery(); 260 this.countQuery = createCountQuery(); 261 this.countDataToUpdateQuery = createCountDataToUpdateQuery(); 262 this.insertStrategy = TableInsertStrategy.INLINE_INSERT; 263 } 264 265 // for tests purposes 266 SynchroTableMetadata() { 267 268 delegate = null; 269 context = null; 270 dbMeta = null; 271 columns = null; 272 columnNames = null; 273 protectedColumnNames = null; 274 joins = null; 275 pkNames = null; 276 pkIndexs = null; 277 withUpdateDateColumn = false; 278 withSynchronizationStatusColumn = false; 279 withIdColumn = false; 280 withRemoteIdColumn = false; 281 isRoot = false; 282 insertQuery = null; 283 insertWithGeneratedIdQuery = null; 284 updateQuery = null; 285 countQuery = null; 286 countDataToUpdateQuery = null; 287 selectPrimaryKeysAsStringQuery = null; 288 selectPrimaryKeysQuery = null; 289 selectRemoteIdsQuery = null; 290 selectMaxUpdateDateQuery = null; 291 selectDataToUpdateQuery = null; 292 selectDataFromFkQueries = null; 293 selectDataFromFkQueriesBigParams = null; 294 selectIdFromRemoteIdQuery = null; 295 selectSequenceNextValueString = null; 296 sequenceNextValString = null; 297 sequenceName = null; 298 selectAllQuery = null; 299 selectDataQueryFromPk = null; 300 } 301 302 /** 303 * Initialize Join metadata. this need to have already loaded all tables. 304 * 305 * @param dbMeta 306 * the Database metadata, with some preloaded tables inside 307 */ 308 public void initJoins(SynchroDatabaseMetadata dbMeta) { 309 try { 310 this.joins = initJoins(getName(), dbMeta, this.columns, this.interceptors); 311 this.hasJoins = !joins.isEmpty(); 312 this.childJoins = initChildJoins(joins); 313 this.parentJoins = initParentJoins(joins); 314 this.hasChildJoins = !childJoins.isEmpty(); 315 this.insertQuery = createInsertQuery(); 316 this.insertStrategy = !hasJoins || !withRemoteIdColumn 317 ? TableInsertStrategy.INLINE_INSERT 318 : TableInsertStrategy.GENERATE_ID_FIRST; 319 } catch (Exception e) { 320 throw new SynchroTechnicalException(t("adagio.persistence.tableMetadata.instanciation.error", this), e); 321 } 322 } 323 324 public Set<String> getPkNames() { 325 return pkNames; 326 } 327 328 public boolean isWithUpdateDateColumn() { 329 return withUpdateDateColumn; 330 } 331 332 public boolean isWithSynchronizationStatusColumn() { 333 return withSynchronizationStatusColumn; 334 } 335 336 public boolean isWithIdColumn() { 337 return withIdColumn; 338 } 339 340 public boolean isWithRemoteIdColumn() { 341 return withRemoteIdColumn; 342 } 343 344 public boolean isRoot() { 345 return isRoot; 346 } 347 348 public int getColumnsCount() { 349 return columnNames.size(); 350 } 351 352 public Set<String> getColumnNames() { 353 return ImmutableSet.copyOf(columnNames); 354 } 355 356 public int getColumnIndex(String name) { 357 return columnNames.indexOf(name.toLowerCase()); 358 } 359 360 public TableMetadata getDelegate() { 361 return delegate; 362 } 363 364 public SynchroDatabaseMetadata getDatabaseMetadata() { 365 return dbMeta; 366 } 367 368 public String getName() { 369 return delegate.getName(); 370 } 371 372 public ForeignKeyMetadata getForeignKeyMetadata(ForeignKey fk) { 373 return delegate.getForeignKeyMetadata(fk); 374 } 375 376 public SynchroColumnMetadata getColumnMetadata(String columnName) { 377 return columns.get(StringHelper.toLowerCase(columnName)); 378 } 379 380 public String getSchema() { 381 return delegate.getSchema(); 382 } 383 384 public String getCatalog() { 385 return delegate.getCatalog(); 386 } 387 388 public ForeignKeyMetadata getForeignKeyMetadata(String keyName) { 389 return delegate.getForeignKeyMetadata(keyName); 390 } 391 392 public IndexMetadata getIndexMetadata(String indexName) { 393 return delegate.getIndexMetadata(indexName); 394 } 395 396 public SynchroColumnMetadata getColumn(String columnName) { 397 return columns.get(columnName); 398 } 399 400 public List<SynchroJoinMetadata> getJoins() { 401 return joins; 402 } 403 404 public List<SynchroJoinMetadata> getChildJoins() { 405 return childJoins; 406 } 407 408 public List<SynchroJoinMetadata> getParentJoins() { 409 return parentJoins; 410 } 411 412 public boolean hasJoins() { 413 return hasJoins; 414 } 415 416 public boolean hasChildJoins() { 417 return hasChildJoins; 418 } 419 420 public String getTableLogPrefix() { 421 return "[" + getName() + "]"; 422 } 423 424 public TableInsertStrategy getInsertStrategy() { 425 return this.insertStrategy; 426 } 427 428 @Override 429 public String toString() { 430 return delegate.toString(); 431 } 432 433 // ------------------------------------------------------------------------// 434 // -- queries methods --// 435 // ------------------------------------------------------------------------// 436 437 public String getInsertQuery() { 438 return insertQuery; 439 } 440 441 public String getInsertWithGeneratedIdQuery() { 442 return insertWithGeneratedIdQuery; 443 } 444 445 public String getUpdateQuery() { 446 return updateQuery; 447 } 448 449 /** 450 * Obtains a SQL with one column output : a concatenation of all PK. 451 * 452 * @return a SQL select, with only one result column 453 */ 454 public String getSelectPrimaryKeysAsStringQuery() { 455 return selectPrimaryKeysAsStringQuery; 456 } 457 458 public String getSelectPrimaryKeysQuery() { 459 return selectPrimaryKeysQuery; 460 } 461 462 public String getSelectRemoteIdsQuery() { 463 return selectRemoteIdsQuery; 464 } 465 466 public String getSequenceNextValString() { 467 return sequenceNextValString; 468 } 469 470 public String getSelectMaxUpdateDateQuery() { 471 return selectMaxUpdateDateQuery; 472 } 473 474 public String getSelectDataQueryFromPk() { 475 return selectDataQueryFromPk; 476 } 477 478 public String getSelectDataToUpdateQuery(Date fromDate) { 479 String sql; 480 if (fromDate == null) { 481 sql = selectAllQuery; 482 } 483 else { 484 sql = selectDataToUpdateQuery; 485 } 486 487 return sql; 488 } 489 490 public String getSelectDataFromFkQuery(String columnName, int nbValues) { 491 String sql = selectDataFromFkQueries.get(columnName.toLowerCase()); 492 StringBuilder builder = new StringBuilder(); 493 for (int i = 0; i < nbValues; i++) { 494 builder.append(",?"); 495 } 496 return String.format(sql, 497 builder.substring(1)); 498 } 499 500 public String getSelectDataFromFkQueryBigParams(String columnName) { 501 return selectDataFromFkQueriesBigParams.get(columnName.toLowerCase()); 502 } 503 504 public String getCountQuery() { 505 return countQuery; 506 } 507 508 public String getCountDataToUpdateQuery(Date fromDate) { 509 String sql; 510 if (fromDate == null) { 511 sql = countQuery; 512 } 513 else { 514 sql = countDataToUpdateQuery; 515 } 516 return sql; 517 } 518 519 public String getSelectIdFromRemoteIdQuery(String tableName) { 520 return String.format(selectIdFromRemoteIdQuery, tableName); 521 } 522 523 public Object[] getData(ResultSet incomingData) throws SQLException { 524 Object[] result = new Object[columnNames.size()]; 525 for (int c = 1; c <= columnNames.size(); c++) { 526 Object object = getObject(incomingData, c); 527 528 result[c - 1] = object; 529 } 530 531 return result; 532 } 533 534 public Object getObject(ResultSet incomingData, int index) throws SQLException { 535 Object object = incomingData.getObject(index); 536 if (object instanceof TIMESTAMP) { 537 object = ((TIMESTAMP) object).timestampValue(); 538 } 539 if (object instanceof BigDecimal) { 540 object = ((BigDecimal) object).intValue(); 541 } 542 return object; 543 } 544 545 // ------------------------------------------------------------------------// 546 // -- PK methods --// 547 // ------------------------------------------------------------------------// 548 549 public int[] getPkIndexs() { 550 return pkIndexs; 551 } 552 553 public boolean isSimpleKey() { 554 return pkIndexs.length == 1; 555 } 556 557 public List<Object> getPk(ResultSet incomingData) throws SQLException { 558 List<Object> result = Lists.newArrayListWithCapacity(pkIndexs.length); 559 for (int pkIndex : pkIndexs) { 560 Object pk = getObject(incomingData, pkIndex); 561 result.add(pk); 562 } 563 return result; 564 } 565 566 public boolean isSelectPrimaryKeysAsStringQueryEnable() { 567 return selectPrimaryKeysAsStringQuery != null; 568 } 569 570 public List<Object> getPk(Object[] incomingData) throws SQLException { 571 List<Object> result = Lists.newArrayListWithCapacity(pkIndexs.length); 572 for (int pkIndex : pkIndexs) { 573 Object pk = incomingData[pkIndex - 1]; 574 result.add(pk); 575 } 576 return result; 577 } 578 579 public Integer getId(ResultSet incomingData) throws SQLException { 580 return incomingData.getInt(pkIndexs[0]); 581 } 582 583 /** 584 * Serialize into a String a list of PK value. Usefull when more than one PK column in a table 585 * 586 * @param pkList 587 * @return 588 */ 589 public String toPkStr(List<Object> pkList) { 590 StringBuilder sb = new StringBuilder(); 591 for (Object pk : pkList) { 592 sb.append(PK_SEPARATOR).append(pk); 593 } 594 return sb.substring(PK_SEPARATOR.length()); 595 } 596 597 public List<Object> fromPkStr(String pk) { 598 List<Object> pkList = Lists.newArrayList(); 599 String[] split = pk.split(PK_SEPARATOR); 600 for (String s : split) { 601 if ("null".equals(s)) { 602 s = null; 603 } 604 pkList.add(s); 605 } 606 return pkList; 607 } 608 609 // ------------------------------------------------------------------------// 610 // -- Protected methods --// 611 // ------------------------------------------------------------------------// 612 613 protected Map<String, SynchroColumnMetadata> initColumns( 614 String tableName, 615 SynchroDatabaseMetadata dbMeta, 616 Predicate<SynchroColumnMetadata> columnFilter 617 ) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { 618 619 Map<String, ColumnMetadata> delegateColumns = getColumns(delegate); 620 Map<String, SynchroColumnMetadata> columns = Maps.newHashMap(); 621 int columnIndex = 1; 622 for (String columnName : delegateColumns.keySet()) { 623 ColumnMetadata delegateColumn = delegateColumns.get(columnName); 624 SynchroColumnMetadata column = new SynchroColumnMetadata( 625 delegateColumn, 626 tableName, 627 columnIndex, 628 PROTECTED_COLUMN_NAMES.contains(columnName)); 629 if (columnFilter == null || columnFilter.apply(column)) { 630 columns.put(columnName, column); 631 columnIndex++; 632 } 633 } 634 635 return columns; 636 } 637 638 protected List<String> initProtectedColumnNames(Map<String, SynchroColumnMetadata> columns) { 639 List<String> result = Lists.newArrayListWithExpectedSize(PROTECTED_COLUMN_NAMES.size()); 640 for (String name : columns.keySet()) { 641 if (PROTECTED_COLUMN_NAMES.contains(name.toLowerCase())) { 642 result.add(name.toLowerCase()); 643 } 644 } 645 return result; 646 } 647 648 protected List<String> initColumnNames(Map<String, SynchroColumnMetadata> columns) { 649 List<String> result = Lists.newArrayListWithExpectedSize(columns.size()); 650 for (String name : columns.keySet()) { 651 if (PROTECTED_COLUMN_NAMES.contains(name.toLowerCase()) == false) { 652 result.add(name.toLowerCase()); 653 } 654 } 655 return result; 656 } 657 658 protected Set<String> initPrimaryKeys(SynchroDatabaseMetadata dbMeta) throws SQLException { 659 660 Set<String> result = Sets.newLinkedHashSet(); 661 ResultSet rs = dbMeta.getPrimaryKeys(getCatalog(), getSchema(), getName()); 662 try { 663 664 while (rs.next()) { 665 result.add(rs.getString("COLUMN_NAME").toLowerCase()); 666 } 667 return ImmutableSet.copyOf(result); 668 } finally { 669 DaoUtils.closeSilently(rs); 670 } 671 } 672 673 protected int[] createPkIndex() { 674 675 int[] result = new int[pkNames.size()]; 676 677 int pkI = 0; 678 for (String pkName : pkNames) { 679 String pkColumnName = pkName.toLowerCase(); 680 681 int i = 1; 682 683 int index = -1; 684 for (String columnName : columnNames) { 685 if (pkColumnName.equals(columnName)) { 686 index = i; 687 } else { 688 i++; 689 } 690 } 691 result[pkI++] = index; 692 } 693 return result; 694 } 695 696 protected List<SynchroJoinMetadata> initJoins( 697 String tableName, 698 SynchroDatabaseMetadata dbMeta, 699 Map<String, SynchroColumnMetadata> columns, 700 List<SynchroInterceptor> interceptors 701 ) throws SQLException { 702 703 List<SynchroJoinMetadata> result = Lists.newArrayList(); 704 705 // Exported keys (primary keys referenced by another table) 706 ResultSet rs = dbMeta.getExportedKeys(delegate.getCatalog(), delegate.getSchema(), tableName); 707 while (rs.next()) { 708 String columnName = rs.getString("PKCOLUMN_NAME"); 709 if (columns.get(columnName.toLowerCase()) != null) { 710 SynchroJoinMetadata join = new SynchroJoinMetadata(rs, this, dbMeta); 711 712 // Fire event to interceptors 713 fireOnJoinLoad(join); 714 715 if (join.isValid()) { 716 result.add(join); 717 } 718 } 719 } 720 721 // Imported keys (foreign keys that references another table) 722 rs = dbMeta.getImportedKeys(delegate.getCatalog(), delegate.getSchema(), tableName); 723 while (rs.next()) { 724 String columnName = rs.getString("FKCOLUMN_NAME"); 725 if (columns.get(columnName.toLowerCase()) != null) { 726 SynchroJoinMetadata join = new SynchroJoinMetadata(rs, this, dbMeta); 727 728 // Fire event to interceptors 729 fireOnJoinLoad(join); 730 731 if (join.isValid()) { 732 result.add(join); 733 } 734 } 735 } 736 737 return result; 738 } 739 740 protected List<SynchroJoinMetadata> initChildJoins( 741 List<SynchroJoinMetadata> joins 742 ) throws SQLException { 743 744 List<SynchroJoinMetadata> result = Lists.newArrayListWithExpectedSize(joins.size()); 745 746 for (SynchroJoinMetadata join : joins) { 747 if (join.isChild()) { 748 result.add(join); 749 } 750 } 751 return result; 752 } 753 754 protected List<SynchroJoinMetadata> initParentJoins( 755 List<SynchroJoinMetadata> joins 756 ) throws SQLException { 757 758 List<SynchroJoinMetadata> result = Lists.newArrayListWithExpectedSize(joins.size()); 759 760 for (SynchroJoinMetadata join : joins) { 761 if (!join.isChild()) { 762 result.add(join); 763 } 764 } 765 return result; 766 } 767 768 protected String createInsertQuery() { 769 return createInsertQuery(false); 770 } 771 772 protected String createInsertWithGeneratedIdQuery() { 773 if (!withRemoteIdColumn) { 774 return null; 775 } 776 return createInsertQuery(true); 777 } 778 779 protected String createInsertQuery(boolean generateId) { 780 if (CollectionUtils.isEmpty(columnNames)) { 781 return null; 782 } 783 784 StringBuilder queryParams = new StringBuilder(); 785 StringBuilder valueParams = new StringBuilder(); 786 787 for (String columnName : columnNames) { 788 if (withRemoteIdColumn && COLUMN_ID.equals(columnName)) { 789 queryParams.append(", ").append(COLUMN_REMOTE_ID); 790 } 791 else { 792 queryParams.append(", ").append(columnName); 793 } 794 valueParams.append(", ?"); 795 } 796 797 if (withRemoteIdColumn) { 798 queryParams.append(", ").append(COLUMN_ID); 799 if (generateId) { 800 valueParams.append(", ").append(selectSequenceNextValueString); 801 } 802 else { 803 valueParams.append(", ?"); 804 } 805 } 806 807 if (withSynchronizationStatusColumn) { 808 queryParams.append(", ").append(COLUMN_SYNCHRONIZATION_STATUS); 809 valueParams.append(", '") 810 .append(SynchroConfiguration.getInstance().getSynchronizationStatusSynchronized()) 811 .append("'"); 812 } 813 814 String result = String.format("INSERT INTO %s (%s) VALUES (%s)", 815 getName(), 816 queryParams.substring(2), 817 valueParams.substring(2)); 818 return result; 819 } 820 821 protected String createUpdateQuery() { 822 // Could not update, because no PK found 823 if (CollectionUtils.isEmpty(pkNames)) { 824 return null; 825 } 826 if (CollectionUtils.isEmpty(columnNames)) { 827 return null; 828 } 829 830 StringBuilder updateParams = new StringBuilder(); 831 832 for (String columnName : columnNames) { 833 if (withRemoteIdColumn && COLUMN_ID.equals(columnName)) { 834 updateParams.append(", ").append(COLUMN_REMOTE_ID); 835 } 836 else { 837 updateParams.append(", ").append(columnName); 838 } 839 updateParams.append(" = ?"); 840 } 841 842 if (withSynchronizationStatusColumn) { 843 updateParams.append(", ").append(COLUMN_SYNCHRONIZATION_STATUS) 844 .append(" = '") 845 .append(SynchroConfiguration.getInstance().getSynchronizationStatusSynchronized()) 846 .append("'"); 847 } 848 849 String result = String.format("UPDATE %s SET %s WHERE %s", 850 getName(), 851 updateParams.substring(2), 852 createPkWhereClause()); 853 return result; 854 } 855 856 protected String createSelectDataFromPkQuery() { 857 // Could not update, because no PK found 858 if (CollectionUtils.isEmpty(pkNames)) { 859 return null; 860 } 861 862 String sql = String.format("SELECT %s FROM %s WHERE %s", 863 createSelectParams(), 864 getName(), 865 createPkWhereClause()); 866 867 sql = fireOnCreateSelectQuery("selectDataFromPkQuery", sql); 868 return sql; 869 } 870 871 protected Map<String, String> createSelectDataFromFkQueries() { 872 Map<String, String> result = Maps.newHashMap(); 873 874 for (SynchroColumnMetadata column : columns.values()) { 875 String sql = String.format("SELECT %s FROM %s WHERE %s IN (%s)", 876 createSelectParams(), 877 getName(), 878 column.getName(), 879 "%s"); 880 881 sql = fireOnCreateSelectQuery("selectDataFromFkQuery", sql); 882 result.put(column.getName().toLowerCase(), sql); 883 } 884 885 return result; 886 } 887 888 protected Map<String, String> createSelectDataFromFkQueriesBigParams(SynchroDatabaseMetadata dbMeta) { 889 Map<String, String> result = Maps.newHashMap(); 890 891 for (SynchroColumnMetadata column : columns.values()) { 892 String sql = String.format( 893 "SELECT %s FROM %s t INNER JOIN TEMP_QUERY_PARAMETER p on t.%s = p.NUMERICAL_VALUE AND p.PARAMETER_NAME=? AND p.PERSON_FK=?", 894 createSelectParams("t"), 895 getName(), 896 column.getName()); 897 898 sql = fireOnCreateSelectQuery("selectDataFromFkQueryBigParams", sql); 899 result.put(column.getName().toLowerCase(), sql); 900 } 901 902 return result; 903 } 904 905 protected String createSelectPrimaryKeysAsStringQuery() { 906 // Could not update, because no PK found 907 if (CollectionUtils.isEmpty(pkNames)) { 908 return null; 909 } 910 911 String prefix = " || '" + PK_SEPARATOR + "' || "; 912 StringBuilder pkParams = new StringBuilder(); 913 914 boolean allowUniqueOutputColumn = true; 915 916 for (String columnName : pkNames) { 917 918 // If some date conversion => not database independent 919 SynchroColumnMetadata column = columns.get(columnName); 920 921 // For date/timestamp : make sure the convertion to char give the same result 922 // -> add a cast to Timestamp 923 if (column.getTypeCode() == Types.TIMESTAMP 924 || column.getTypeCode() == Types.DATE) { 925 allowUniqueOutputColumn = false; 926 } 927 pkParams.append(prefix).append(columnName); 928 } 929 930 if (!allowUniqueOutputColumn) { 931 return null; 932 } 933 934 String sql = String.format("SELECT %s FROM %s", 935 pkParams.substring(prefix.length()), 936 getName()); 937 938 sql = fireOnCreateSelectQuery("selectPrimaryKeysAsStringQuery", sql); 939 return sql; 940 } 941 942 protected String createSelectPrimaryKeysQuery() { 943 // Could not update, because no PK found 944 if (CollectionUtils.isEmpty(pkNames)) { 945 return null; 946 } 947 948 StringBuilder pkParams = new StringBuilder(); 949 950 for (String columnName : pkNames) { 951 pkParams.append(", ").append(columnName); 952 } 953 954 String sql = String.format("SELECT %s FROM %s", 955 pkParams.substring(2), 956 getName()); 957 958 sql = fireOnCreateSelectQuery("selectPrimaryKeysQuery", sql); 959 960 return sql; 961 } 962 963 protected String createSelectRemoteIdsQuery() { 964 // Could not update, because no PK found 965 if (!withRemoteIdColumn || !withIdColumn) { 966 return null; 967 } 968 969 String sql = String.format("SELECT %s, %s FROM %s WHERE %s IS NOT NULL", 970 COLUMN_ID, 971 COLUMN_REMOTE_ID, 972 getName(), 973 COLUMN_REMOTE_ID); 974 975 sql = fireOnCreateSelectQuery("selectRemoteIdsQuery", sql); 976 977 return sql; 978 } 979 980 protected String createSelectAllQuery() { 981 String sql = String.format("SELECT %s FROM %s t", 982 createSelectParams("t"), 983 getName() 984 ); 985 986 sql = fireOnCreateSelectQuery("selectAllQuery", sql); 987 988 return sql; 989 } 990 991 protected String createSelectMaxUpdateDateQuery() { 992 if (!withUpdateDateColumn) { 993 return null; 994 } 995 String sql = String.format("SELECT max(%s) FROM %s", 996 COLUMN_UPDATE_DATE, 997 getName()); 998 999 sql = fireOnCreateSelectQuery("selectMaxUpdateDateQuery", sql); 1000 1001 return sql; 1002 } 1003 1004 protected String createSelectDataToUpdateQuery() { 1005 String sql = String.format("SELECT %s FROM %s t%s", 1006 createSelectParams("t"), 1007 getName(), 1008 createWithUpdateDateWhereClause("t")); 1009 1010 sql = fireOnCreateSelectQuery("selectDataToUpdateQuery", sql); 1011 1012 return sql; 1013 } 1014 1015 protected String createCountQuery() { 1016 String sql = String.format("SELECT count(*) FROM %s t", 1017 getName()); 1018 1019 sql = fireOnCreateSelectQuery("countQuery", sql); 1020 1021 return sql; 1022 } 1023 1024 protected String createSelectIdFromRemoteIdQuery() { 1025 if (!withIdColumn || withRemoteIdColumn) { 1026 return null; 1027 } 1028 String sql = String.format("SELECT %s FROM %s WHERE %s=?", 1029 SynchroTableMetadata.COLUMN_ID, 1030 "%s", 1031 SynchroTableMetadata.COLUMN_REMOTE_ID 1032 ); 1033 1034 sql = fireOnCreateSelectQuery("selectIdFromRemoteIdQuery", sql); 1035 1036 return sql; 1037 } 1038 1039 protected String createCountDataToUpdateQuery() { 1040 String sql = String.format("SELECT count(*) FROM %s t%s", 1041 getName(), 1042 createWithUpdateDateWhereClause("t")); 1043 1044 sql = fireOnCreateSelectQuery("countDataToUpdateQuery", sql); 1045 1046 return sql; 1047 } 1048 1049 protected String createPkWhereClause() { 1050 Preconditions.checkArgument(CollectionUtils.isNotEmpty(pkNames)); 1051 StringBuilder pkParams = new StringBuilder(); 1052 1053 for (String columnName : pkNames) { 1054 pkParams.append("AND ").append(columnName).append(" = ?"); 1055 } 1056 1057 return pkParams.substring(4); 1058 } 1059 1060 protected String createWithUpdateDateWhereClause() { 1061 return createWithUpdateDateWhereClause(null); 1062 } 1063 1064 protected String createWithUpdateDateWhereClause(String tableAlias) { 1065 String whereClause; 1066 1067 if (isWithUpdateDateColumn()) { 1068 String prefix = tableAlias != null ? tableAlias + "." : ""; 1069 // add a filter 1070 whereClause = String.format( 1071 " WHERE (%supdate_date IS NULL OR %supdate_date > ?)", 1072 prefix, 1073 prefix); 1074 } else { 1075 whereClause = ""; 1076 } 1077 return whereClause; 1078 } 1079 1080 protected String createSelectParams() { 1081 return createSelectParams(null); 1082 } 1083 1084 protected String createSelectParams(String tableAlias) { 1085 Preconditions.checkArgument(CollectionUtils.isNotEmpty(columnNames), 1086 String.format("No column found for table: %s", delegate.getName())); 1087 1088 StringBuilder queryParams = new StringBuilder(); 1089 1090 for (String columnName : columnNames) { 1091 queryParams.append(", "); 1092 if (tableAlias != null) { 1093 queryParams.append(tableAlias) 1094 .append("."); 1095 } 1096 queryParams.append(columnName); 1097 } 1098 1099 return queryParams.substring(2); 1100 } 1101 1102 protected String initSequenceName(Set<String> availableSequences) { 1103 1104 String tableName = getName().toLowerCase(); 1105 String sequenceName = tableName + SEQUENCE_SUFFIX; 1106 if (availableSequences.contains(sequenceName.toLowerCase())) { 1107 return sequenceName; 1108 } 1109 1110 int maxLengthWithoutSuffix = 30 - SEQUENCE_SUFFIX.length(); 1111 if (tableName.length() > maxLengthWithoutSuffix) { 1112 sequenceName = tableName.substring(0, maxLengthWithoutSuffix) + SEQUENCE_SUFFIX; 1113 if (availableSequences.contains(sequenceName.toLowerCase())) { 1114 return sequenceName; 1115 } 1116 } 1117 1118 return null; 1119 } 1120 1121 public void setIsRoot(boolean isRoot) { 1122 this.isRoot = isRoot; 1123 } 1124 1125 protected String createSelectSequenceNextValString(Dialect dialect) { 1126 if (StringUtils.isBlank(sequenceName)) { 1127 return null; 1128 } 1129 return dialect.getSelectSequenceNextValString(sequenceName); 1130 } 1131 1132 protected String createSequenceNextValString(Dialect dialect) { 1133 if (StringUtils.isBlank(sequenceName)) { 1134 return null; 1135 } 1136 return dialect.getSequenceNextValString(sequenceName); 1137 } 1138 1139 public List<SynchroInterceptor> getInterceptors() { 1140 return this.interceptors; 1141 } 1142 1143 protected String fireOnCreateSelectQuery(String queryName, String sql) { 1144 if (CollectionUtils.isNotEmpty(interceptors)) { 1145 for (SynchroInterceptor interceptor : interceptors) { 1146 String newSql = interceptor.onCreateSelectQuery(this, queryName, sql); 1147 if (newSql != null) { 1148 sql = newSql; 1149 } 1150 } 1151 } 1152 return sql; 1153 } 1154 1155 protected void fireOnJoinLoad(SynchroJoinMetadata join) { 1156 if (CollectionUtils.isNotEmpty(interceptors)) { 1157 for (SynchroInterceptor interceptor : interceptors) { 1158 interceptor.onJoinLoad(this, join); 1159 } 1160 } 1161 } 1162}