001/* 002 * Copyright 2010 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.forms.table; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.net.URISyntaxException; 021import java.sql.Connection; 022import java.sql.DatabaseMetaData; 023import java.sql.PreparedStatement; 024import java.sql.ResultSet; 025import java.sql.SQLException; 026import java.sql.Timestamp; 027import java.sql.Types; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.HashMap; 031import java.util.LinkedHashMap; 032import java.util.List; 033import java.util.Locale; 034import java.util.Map; 035import java.util.Map.Entry; 036import java.util.Set; 037 038import org.apache.avalon.framework.component.Component; 039import org.apache.avalon.framework.service.ServiceException; 040import org.apache.avalon.framework.service.ServiceManager; 041import org.apache.avalon.framework.service.Serviceable; 042import org.apache.cocoon.ProcessingException; 043import org.apache.commons.lang.StringUtils; 044import org.slf4j.LoggerFactory; 045 046import org.ametys.cms.repository.Content; 047import org.ametys.core.datasource.ConnectionHelper; 048import org.ametys.core.datasource.dbtype.SQLDatabaseTypeExtensionPoint; 049import org.ametys.core.ui.Callable; 050import org.ametys.core.util.I18nUtils; 051import org.ametys.core.util.URIUtils; 052import org.ametys.plugins.forms.Field; 053import org.ametys.plugins.forms.Field.FieldType; 054import org.ametys.plugins.forms.Form; 055import org.ametys.plugins.forms.FormsException; 056import org.ametys.plugins.forms.data.FieldValue; 057import org.ametys.plugins.forms.data.UserEntry; 058import org.ametys.plugins.forms.jcr.FormPropertiesManager; 059import org.ametys.plugins.repository.AmetysObjectResolver; 060import org.ametys.plugins.workflow.store.JdbcWorkflowStore; 061import org.ametys.runtime.config.Config; 062import org.ametys.runtime.i18n.I18nizableText; 063 064/** 065 * Class that handles creation and deletion of a table used by a form. 066 */ 067public class FormTableManager implements Component, Serviceable 068{ 069 /** Avalon Role */ 070 public static final String ROLE = FormTableManager.class.getName(); 071 072 /** The id of the configuration parameter used for the forms' datasource */ 073 public static final String FORMS_POOL_CONFIG_PARAM = "plugins.forms.datasource"; 074 075 /** Prefix for database tables */ 076 public static final String TABLE_PREFIX = "Forms_"; 077 078 /** ID field name. */ 079 public static final String ID_FIELD = "id"; 080 081 /** Creation date field name. */ 082 public static final String CREATION_DATE_FIELD = "creationDate"; 083 084 /** Workflow id column name. */ 085 public static final String WORKFLOW_ID_FIELD = "workflowId"; 086 087 /** Suffix for filename column. */ 088 public static final String FILE_NAME_COLUMN_SUFFIX = "-filename"; 089 090 /** Constant for table state */ 091 public static final int TABLE_CREATED_AND_UP_TO_DATE = 2; 092 093 /** Constant for table state */ 094 public static final int TABLE_CREATED_BUT_NEED_UPDATE = 3; 095 096 /** Constant for table state */ 097 public static final int TABLE_NOT_CREATED = 1; 098 099 /** Constant for table state */ 100 public static final int TABLE_UNKOWN_STATUS = 0; 101 102 private static org.slf4j.Logger __logger = LoggerFactory.getLogger(FormTableManager.class); 103 104 private ServiceManager _manager; 105 106 private FormPropertiesManager _formPropertiesManager; 107 private AmetysObjectResolver _resolver; 108 private I18nUtils _i18nUtils; 109 private JdbcWorkflowStore _jdbcWorkflowStore; 110 private SQLDatabaseTypeExtensionPoint _sqlDatabaseTypeExtensionPoint; 111 112 @Override 113 public void service(ServiceManager smanager) throws ServiceException 114 { 115 _manager = smanager; 116 } 117 118 private SQLDatabaseTypeExtensionPoint getSQLDatabaseTypeExtensionPoint() 119 { 120 if (_sqlDatabaseTypeExtensionPoint == null) 121 { 122 try 123 { 124 _sqlDatabaseTypeExtensionPoint = (SQLDatabaseTypeExtensionPoint) _manager.lookup(SQLDatabaseTypeExtensionPoint.ROLE); 125 } 126 catch (ServiceException e) 127 { 128 throw new RuntimeException(e); 129 } 130 } 131 return _sqlDatabaseTypeExtensionPoint; 132 } 133 134 private I18nUtils getI18nUtils() 135 { 136 if (_i18nUtils == null) 137 { 138 try 139 { 140 _i18nUtils = (I18nUtils) _manager.lookup(I18nUtils.ROLE); 141 } 142 catch (ServiceException e) 143 { 144 throw new RuntimeException(e); 145 } 146 } 147 return _i18nUtils; 148 } 149 150 private AmetysObjectResolver getAmetysObjectResolver() 151 { 152 if (_resolver == null) 153 { 154 try 155 { 156 _resolver = (AmetysObjectResolver) _manager.lookup(AmetysObjectResolver.ROLE); 157 } 158 catch (ServiceException e) 159 { 160 throw new RuntimeException(e); 161 } 162 } 163 return _resolver; 164 } 165 166 private FormPropertiesManager getFormPropertiesManager() 167 { 168 if (_formPropertiesManager == null) 169 { 170 try 171 { 172 _formPropertiesManager = (FormPropertiesManager) _manager.lookup(FormPropertiesManager.ROLE); 173 } 174 catch (ServiceException e) 175 { 176 throw new RuntimeException(e); 177 } 178 } 179 return _formPropertiesManager; 180 } 181 182 private JdbcWorkflowStore getJdbcWorkflowStore() 183 { 184 if (_jdbcWorkflowStore == null) 185 { 186 try 187 { 188 _jdbcWorkflowStore = (JdbcWorkflowStore) _manager.lookup(JdbcWorkflowStore.ROLE); 189 } 190 catch (ServiceException e) 191 { 192 throw new RuntimeException(e); 193 } 194 } 195 return _jdbcWorkflowStore; 196 } 197 198 /** 199 * Create database table to store results of content forms 200 * @param form informations used to create table 201 * @return true if the table was created false else 202 */ 203 public boolean createTable(Form form) 204 { 205 if (form == null) 206 { 207 throw new IllegalArgumentException("Form object can not be null"); 208 } 209 210 Map<String, FieldValue> columns = getColumns(form); 211 if (!_createOrUpdateTable(form.getId(), columns)) 212 { 213 return false; 214 } 215 return true; 216 } 217 218 /** 219 * Drop the table by its name 220 * @param table the name of the table to drop 221 * @return true if the table was dropped, false otherwise 222 */ 223 public boolean dropTable(String table) 224 { 225 PreparedStatement stmt = null; 226 Connection connection = null; 227 228 try 229 { 230 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 231 connection = ConnectionHelper.getConnection(dataSourceId); 232 233 String request = "DROP TABLE " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(ConnectionHelper.getDatabaseType(connection), table); 234 235 stmt = connection.prepareStatement(request); 236 stmt.executeUpdate(); 237 } 238 catch (SQLException e) 239 { 240 __logger.error("Error while deleting table " + table, e); 241 return false; 242 } 243 finally 244 { 245 ConnectionHelper.cleanup(stmt); 246 ConnectionHelper.cleanup(connection); 247 } 248 249 return true; 250 } 251 252 /** 253 * Get all the table names 254 * @return the list of all the forms' table names 255 */ 256 public List<String> getTableNames() 257 { 258 List<String> formsTableNames = new ArrayList<> (); 259 Connection con = null; 260 PreparedStatement stmt = null; 261 DatabaseMetaData metadata = null; 262 ResultSet tables = null; 263 264 try 265 { 266 // Use a connection pool to establish a connection to the data base 267 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 268 con = ConnectionHelper.getConnection(dataSourceId); 269 metadata = con.getMetaData(); 270 tables = metadata.getTables(null, null, "%", null); 271 272 while (tables.next()) 273 { 274 String tableName = tables.getString(3); 275 if (tableName.startsWith(TABLE_PREFIX)) 276 { 277 formsTableNames.add(tableName); 278 } 279 } 280 } 281 catch (SQLException e) 282 { 283 __logger.error("Error while retrieving the forms table names", e); 284 } 285 finally 286 { 287 // Clean up connection resources 288 ConnectionHelper.cleanup(stmt); 289 ConnectionHelper.cleanup(con); 290 } 291 292 return formsTableNames; 293 } 294 295 /** 296 * Get all the submissions for a form. 297 * @param form the form object. 298 * @param columns the columns 299 * @param offset The number of results to ignore. 300 * @param length The maximum number of results to return 301 * @param entryIds the ids of the submissions to retrieve 302 * @return the list of submissions. 303 * @throws FormsException if an error occurs. 304 */ 305 public List<UserEntry> getSubmissions(Form form, Map<String, FieldValue> columns, int offset, int length, List<Integer> entryIds) throws FormsException 306 { 307 List<UserEntry> entries = new ArrayList<>(); 308 309 final String tableName = TABLE_PREFIX + form.getId(); 310 311 Connection connection = null; 312 PreparedStatement stmt = null; 313 ResultSet rs = null; 314 315 try 316 { 317 318 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 319 connection = ConnectionHelper.getConnection(dataSourceId); 320 String dbType = ConnectionHelper.getDatabaseType(connection); 321 322 String request = _getSubmissionsQuery(offset, length, tableName, dbType); 323 324 stmt = connection.prepareStatement(request); 325 326 // Execute the query. 327 rs = stmt.executeQuery(); 328 329 // Extract the results. 330 while (rs.next()) 331 { 332 // First get the ID and submission date. 333 int id = rs.getInt(ID_FIELD); 334 Timestamp creationDate = rs.getTimestamp(CREATION_DATE_FIELD); 335 336 List<FieldValue> entryValues = new ArrayList<>(); 337 338 for (Map.Entry<String, FieldValue> column : columns.entrySet()) 339 { 340 String columnLabel = column.getKey(); 341 FieldValue columnValue = column.getValue(); 342 343 // Extract the value. 344 FieldValue value = null; 345 346 switch (columnValue.getType()) 347 { 348 case Types.VARCHAR: 349 case Types.LONGVARCHAR: 350 String sValue = rs.getString(columnLabel); 351 value = new FieldValue(columnValue); 352 value.setValue(sValue); 353 entryValues.add(value); 354 break; 355 case Types.BOOLEAN: 356 Boolean bValue = rs.getBoolean(columnLabel); 357 value = new FieldValue(columnValue); 358 value.setValue(bValue); 359 entryValues.add(value); 360 break; 361 case Types.BLOB: 362 String fileNameColumn = columnLabel + FILE_NAME_COLUMN_SUFFIX; 363 String normalizedName = DbTypeHelper.normalizeName(dbType, fileNameColumn); 364 String fileName = rs.getString(normalizedName); 365 value = new FieldValue(columnValue); 366 367 try (InputStream isValue = _sqlDatabaseTypeExtensionPoint.getBlob(dbType, rs, columnLabel)) 368 { 369 value.setValue(isValue == null ? null : fileName); 370 } 371 372 entryValues.add(value); 373 break; 374 case Types.INTEGER: 375 Integer iValue = rs.getInt(columnLabel); 376 value = new FieldValue(columnValue); 377 value.setValue(iValue); 378 entryValues.add(value); 379 break; 380 default: 381 break; 382 } 383 } 384 385 Integer workflowId = null; 386 if (hasWorkflowIdColumn(form.getId())) 387 { 388 workflowId = rs.getInt(WORKFLOW_ID_FIELD); 389 } 390 391 if (entryIds == null || entryIds.contains(id)) 392 { 393 UserEntry entry = new UserEntry(id, creationDate, entryValues, workflowId); 394 entries.add(entry); 395 } 396 } 397 } 398 catch (SQLException | IOException e) 399 { 400 __logger.error("Error while getting entries for table " + tableName, e); 401 throw new FormsException("Error while getting entries for table " + tableName, e); 402 } 403 finally 404 { 405 ConnectionHelper.cleanup(rs); 406 ConnectionHelper.cleanup(stmt); 407 ConnectionHelper.cleanup(connection); 408 } 409 410 return entries; 411 } 412 413 /** 414 * Get the submissions query 415 * @param offset The number of results to ignore. 416 * @param length The maximum number of results to return 417 * @param tableName the name of the table 418 * @param dbType the type of the database used 419 * @return the string query for getting the submissions of a form 420 */ 421 private String _getSubmissionsQuery(int offset, int length, final String tableName, String dbType) 422 { 423 return getSQLDatabaseTypeExtensionPoint().languageLimitQuery(dbType, "SELECT * FROM " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName), Integer.toString(length), Integer.toString(offset)); 424 } 425 426 /** 427 * Get the total count of submissions for a form. 428 * @param formId the id of the form 429 * @return the total count of submissions, or -1 if an error occurs. 430 * @throws FormsException if an error occurs. 431 */ 432 public int getTotalSubmissions(String formId) throws FormsException 433 { 434 final String tableName = TABLE_PREFIX + formId; 435 436 Connection connection = null; 437 PreparedStatement stmt = null; 438 ResultSet rs = null; 439 440 try 441 { 442 443 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 444 connection = ConnectionHelper.getConnection(dataSourceId); 445 446 String dbType = ConnectionHelper.getDatabaseType(connection); 447 448 String request = "SELECT COUNT(*) FROM " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName); 449 450 stmt = connection.prepareStatement(request); 451 452 // Execute the query. 453 rs = stmt.executeQuery(); 454 455 // Extract the result. 456 if (rs.next()) 457 { 458 int count = rs.getInt(1); 459 return count; 460 } 461 } 462 catch (SQLException e) 463 { 464 __logger.error("Error while getting entries for table " + tableName, e); 465 throw new FormsException("Error while getting entries for table " + tableName, e); 466 } 467 finally 468 { 469 ConnectionHelper.cleanup(rs); 470 ConnectionHelper.cleanup(stmt); 471 ConnectionHelper.cleanup(connection); 472 } 473 474 return -1; 475 } 476 477 /** 478 * Get the forms of contents 479 * @param contentIds The ids of contents 480 * @param currentLanguage The current language 481 * @return The forms 482 * @throws FormsException if an error occurred while getting content' form 483 * @throws URISyntaxException if an error occurred when URL encoding form's label 484 */ 485 @Callable 486 public List<Map<String, Object>> getContentForms (List<String> contentIds, String currentLanguage) throws FormsException, URISyntaxException 487 { 488 List<Map<String, Object>> result = new ArrayList<>(); 489 490 for (String contentId : contentIds) 491 { 492 Content content = getAmetysObjectResolver().resolveById(contentId); 493 494 Map<String, Object> contentforms = new HashMap<>(); 495 496 contentforms.put("id", content.getId()); 497 contentforms.put("title", content.getTitle(new Locale(currentLanguage))); 498 499 List<Map<String, Object>> forms2json = new ArrayList<>(); 500 501 List<Form> forms = getFormPropertiesManager().getForms(content); 502 for (Form form : forms) 503 { 504 Map<String, Object> form2json = new HashMap<>(); 505 form2json.put("id", form.getId()); 506 form2json.put("label", URIUtils.decode(form.getLabel())); 507 form2json.put("workflowName", StringUtils.defaultString(form.getWorkflowName(), "")); 508 509 forms2json.add(form2json); 510 } 511 512 contentforms.put("forms", forms2json); 513 514 result.add(contentforms); 515 } 516 517 return result; 518 } 519 520 /** 521 * Get the columns information of a form 522 * @param siteName The site name 523 * @param formId the identifier of form 524 * @return The columns 525 * @throws FormsException if an error occurred 526 */ 527 @Callable 528 public List<Map<String, Object>> getColumns (String siteName, String formId) throws FormsException 529 { 530 List<Map<String, Object>> columns2json = new ArrayList<>(); 531 532 Form form = getFormPropertiesManager().getForm(siteName, formId); 533 534 Map<String, FieldValue> columns = getColumns(form); 535 for (FieldValue column : columns.values()) 536 { 537 Map<String, Object> column2json = new HashMap<>(); 538 column2json.put("id", column.getColumnName()); 539 540 Field field = column.getField(); 541 column2json.put("label", field.getLabel()); 542 column2json.put("type", field.getType().toString()); 543 column2json.put("name", field.getName()); 544 column2json.put("properties", field.getProperties()); 545 546 columns2json.add(column2json); 547 } 548 549 return columns2json; 550 } 551 552 /** 553 * Remove a list of tables 554 * @param tableNames the names of the tables to delete 555 * @throws FormsException if the drop of the tables is not successful 556 */ 557 @Callable 558 public void removeTables(List<String> tableNames) throws FormsException 559 { 560 for (String tableName : tableNames) 561 { 562 if (!dropTable(tableName)) 563 { 564 throw new FormsException("An error occurred occured while removing the tables from the database."); 565 } 566 } 567 } 568 569 /** 570 * Form to tableinfo. 571 * @param form the form. 572 * @return the tableinfo. 573 */ 574 public Map<String, FieldValue> getColumns(Form form) 575 { 576 Map<String, FieldValue> columns = new LinkedHashMap<>(); 577 578 for (Field field : form.getFields()) 579 { 580 final String id = field.getId(); 581 final String name = field.getName(); 582 583 FieldValue fdValue = null; 584 585 switch (field.getType()) 586 { 587 case TEXT: 588 case HIDDEN: 589 case PASSWORD: 590 case COST: 591 case SELECT: 592 fdValue = new FieldValue(id, Types.VARCHAR, null, field); 593 columns.put(id, fdValue); 594 break; 595 case TEXTAREA: 596 fdValue = new FieldValue(id, Types.LONGVARCHAR, null, field); 597 columns.put(id, fdValue); 598 break; 599 case RADIO: 600 if (!columns.containsKey(name)) 601 { 602 String value = field.getProperties().get("value"); 603 String label = field.getLabel(); 604 int index = 1; 605 field.getProperties().put("size", String.valueOf(index)); 606 field.getProperties().put("option-" + index + "-value", value); 607 field.getProperties().put("option-" + index + "-label", label); 608 609 field.getProperties().remove("value"); 610 611 fdValue = new FieldValue(name, Types.VARCHAR, null, field); 612 columns.put(name, fdValue); 613 } 614 else 615 { 616 // The value exists, clone it, concatenating the label. 617 if (StringUtils.isNotEmpty(field.getLabel())) 618 { 619 Field radioField = columns.get(name).getField(); 620 int index = Integer.parseInt(radioField.getProperties().get("size")); 621 index++; 622 623 String value = field.getProperties().get("value"); 624 String label = field.getLabel(); 625 626 radioField.getProperties().put("size", String.valueOf(index)); 627 radioField.getProperties().put("option-" + index + "-value", value); 628 radioField.getProperties().put("option-" + index + "-label", label); 629 630 Field dummyField = new Field(radioField.getId(), radioField.getType(), radioField.getName(), radioField.getLabel() + "/" + field.getLabel(), radioField.getProperties()); 631 columns.get(name).setField(dummyField); 632 } 633 } 634 break; 635 case CHECKBOX: 636 fdValue = new FieldValue(id, Types.BOOLEAN, null, field); 637 columns.put(id, fdValue); 638 break; 639 case FILE: 640 fdValue = new FieldValue(id, Types.BLOB, null, field); 641 columns.put(id, fdValue); 642 break; 643 case CAPTCHA: 644 break; 645 default: 646 break; 647 } 648 649 } 650 651 return columns; 652 } 653 654 /** 655 * Get the full column type corresponding to the database type (with precision). 656 * @param sqlType the sqltype value. 657 * @param dbType the database type. 658 * @return the real column type identifier (to be included in CREATE statement). 659 */ 660 protected String _getColumnType(int sqlType, String dbType) 661 { 662 switch (sqlType) 663 { 664 case Types.LONGVARCHAR: 665 return DbTypeHelper.getTextType(dbType); 666 case Types.BOOLEAN: 667 return DbTypeHelper.getBooleanType(dbType); 668 case Types.BLOB: 669 return DbTypeHelper.getBinaryType(dbType); 670 case Types.VARCHAR: 671 default: 672 return DbTypeHelper.getVarcharType(dbType); 673 } 674 } 675 676 /** 677 * Get the column type name (without precision) corresponding to the SQL type. 678 * @param sqlType the SQL type value. 679 * @param dbType the database type. 680 * @return the real column type name (without precision). 681 */ 682 protected String _getColumnTypeName(int sqlType, String dbType) 683 { 684 return StringUtils.substringBefore(_getColumnType(sqlType, dbType), "("); 685 } 686 687 /** 688 * Test if two columns have the same type. 689 * @param column the column retrieved from the database. 690 * @param newColumn the new column. 691 * @param dbType the database type. 692 * @return true if the two columns have the same type. 693 */ 694 protected boolean _isSameType(DbColumn column, FieldValue newColumn, String dbType) 695 { 696 int newColumnType = newColumn.getType(); 697 int currentColumnType = column.getSqlType(); 698 String newColumnTypeName = _getColumnTypeName(newColumnType, dbType); 699 String currentColumnTypeName = column.getTypeName(); 700 701 return currentColumnType == newColumnType || currentColumnTypeName.equals(newColumnTypeName); 702 } 703 704 private boolean _createTable(String formId, Map<String, FieldValue> columns) 705 { 706 String tableName = TABLE_PREFIX + formId; 707 708 Connection connection = null; 709 PreparedStatement stmt = null; 710 711 try 712 { 713 714 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 715 connection = ConnectionHelper.getConnection(dataSourceId); 716 717 String dbType = ConnectionHelper.getDatabaseType(connection); 718 StringBuilder sql = new StringBuilder(); 719 720 sql.append("CREATE TABLE "); 721 sql.append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName)); 722 sql.append(" ( ").append(ID_FIELD).append(" ").append(DbTypeHelper.getIdentityType(dbType)).append(" ").append(DbTypeHelper.getIdentityMarker(dbType)).append(", "); 723 sql.append(CREATION_DATE_FIELD).append(" ").append(DbTypeHelper.getDateTimeType(dbType)).append(" NOT NULL,"); 724 725 for (Map.Entry<String, FieldValue> column : columns.entrySet()) 726 { 727 int sqlType = column.getValue().getType(); 728 sql.append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, column.getKey())).append(" "); 729 sql.append(_getColumnType(sqlType, dbType)); 730 sql.append(" DEFAULT NULL,"); 731 732 // Add a column for the file name. 733 if (sqlType == Types.BLOB) 734 { 735 String fileNameColumn = column.getKey() + FILE_NAME_COLUMN_SUFFIX; 736 String normalizedName = DbTypeHelper.normalizeName(dbType, fileNameColumn); 737 738 sql.append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, normalizedName)).append(" "); 739 sql.append(DbTypeHelper.getVarcharType(dbType)); 740 sql.append(" DEFAULT NULL,"); 741 } 742 } 743 744 sql.append("PRIMARY KEY (").append(ID_FIELD).append("))"); 745 746 if (__logger.isDebugEnabled()) 747 { 748 __logger.debug("Creating table : " + sql.toString()); 749 } 750 751 stmt = connection.prepareStatement(sql.toString()); 752 stmt.executeUpdate(); 753 ConnectionHelper.cleanup(stmt); 754 755 // create sequence for oracle database 756 if (ConnectionHelper.DATABASE_ORACLE.equals(dbType)) 757 { 758 String sqlQuery = "create sequence seq_" + formId; 759 stmt = connection.prepareStatement(sqlQuery); 760 stmt.executeUpdate(); 761 } 762 763 return true; 764 } 765 catch (SQLException e) 766 { 767 __logger.error("Unable to create table " + tableName, e); 768 return false; 769 } 770 finally 771 { 772 ConnectionHelper.cleanup(stmt); 773 ConnectionHelper.cleanup(connection); 774 } 775 } 776 777 private boolean _alterTable(String formId, Map<String, FieldValue> newColumns) 778 { 779 String tableName = TABLE_PREFIX + formId; 780 781 Connection connection = null; 782 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 783 784 try 785 { 786 connection = ConnectionHelper.getConnection(dataSourceId); 787 788 // Start transaction. 789 connection.setAutoCommit(false); 790 791 // Get the existing columns. 792 Map<String, DbColumn> existingColumns = _getExistingColumns(connection, tableName); 793 794 // Compute the modifications to make. 795 DbColumnModifications modifications = _getColumnsToModify(connection, existingColumns, newColumns); 796 797 // Move the columns that were deleted or replaced. 798 _moveColumns(connection, modifications.getColumnsToRemove(), existingColumns, tableName); 799 800 // Add the new columns. 801 _addColumns(connection, modifications.getColumnsToAdd(), tableName); 802 803 // Commit the transaction. 804 connection.commit(); 805 } 806 catch (SQLException e) 807 { 808 __logger.error("Unable to alter table " + tableName, e); 809 try 810 { 811 connection.rollback(); 812 } 813 catch (SQLException sqlex) 814 { 815 // Ignore. 816 __logger.error("Error rollbacking the 'alter table' statements for table " + tableName, e); 817 } 818 return false; 819 } 820 finally 821 { 822 ConnectionHelper.cleanup(connection); 823 } 824 return true; 825 826 } 827 828 private int _checkTableStatus(String formId, Map<String, FieldValue> newColumns) 829 { 830 String tableName = TABLE_PREFIX + formId; 831 832 Connection connection = null; 833 ResultSet tables = null; 834 ResultSet rs = null; 835 836 Map<String, DbColumn> currentColumns = new HashMap<>(); 837 838 try 839 { 840 841 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 842 connection = ConnectionHelper.getConnection(dataSourceId); 843 844 String dbType = ConnectionHelper.getDatabaseType(connection); 845 846 tableName = DbTypeHelper.filterName(dbType, tableName); 847 848 DatabaseMetaData metadata = connection.getMetaData(); 849 tables = metadata.getTables(connection.getCatalog(), null, tableName, null); 850 851 if (!tables.next()) 852 { 853 return TABLE_NOT_CREATED; 854 } 855 856 currentColumns = _getExistingColumns(connection, tableName); 857 858 for (String newColumn : newColumns.keySet()) 859 { 860 String filteredColumn = DbTypeHelper.filterName(dbType, newColumn); 861 862 // Test the existence of the column. 863 if (!currentColumns.containsKey(filteredColumn)) 864 { 865 return TABLE_CREATED_BUT_NEED_UPDATE; 866 } 867 868 // Test that the column type has not changed. 869 DbColumn currentColumn = currentColumns.get(filteredColumn); 870 FieldValue newColumnFv = newColumns.get(newColumn); 871 872 if (!_isSameType(currentColumn, newColumnFv, dbType)) 873 { 874 return TABLE_CREATED_BUT_NEED_UPDATE; 875 } 876 } 877 878 return TABLE_CREATED_AND_UP_TO_DATE; 879 } 880 catch (SQLException e) 881 { 882 __logger.error("Unable to get columns from table " + tableName, e); 883 return TABLE_UNKOWN_STATUS; 884 } 885 finally 886 { 887 ConnectionHelper.cleanup(tables); 888 ConnectionHelper.cleanup(rs); 889 ConnectionHelper.cleanup(connection); 890 } 891 } 892 893 private boolean _createOrUpdateTable(String formId, final Map<String, FieldValue> columns) 894 { 895 boolean toReturn = true; 896 switch (_checkTableStatus(formId, columns)) 897 { 898 case TABLE_NOT_CREATED: 899 if (!_createTable(formId, columns)) 900 { 901 toReturn = false; 902 } 903 break; 904 case TABLE_CREATED_BUT_NEED_UPDATE: 905 if (!_alterTable(formId, columns)) 906 { 907 toReturn = false; 908 } 909 break; 910 case TABLE_CREATED_AND_UP_TO_DATE: 911 case TABLE_UNKOWN_STATUS: 912 default: 913 break; 914 } 915 return toReturn; 916 } 917 918 private DbColumnModifications _getColumnsToModify(Connection con, Map<String, DbColumn> existingColumns, Map<String, FieldValue> newColumns) 919 { 920 DbColumnModifications modifications = new DbColumnModifications(); 921 922 String dbType = ConnectionHelper.getDatabaseType(con); 923 924 Map<String, FieldValue> columnsToAdd = new LinkedHashMap<>(); 925 Map<String, DbColumn> columnsToRemove = new HashMap<>(); 926 for (Map.Entry<String, FieldValue> newColumn : newColumns.entrySet()) 927 { 928 String filteredName = DbTypeHelper.filterName(dbType, newColumn.getKey()); 929 columnsToAdd.put(filteredName, newColumn.getValue()); 930 } 931 932 if (existingColumns != null) 933 { 934 for (String existingColName : existingColumns.keySet()) 935 { 936 DbColumn existingColumn = existingColumns.get(existingColName); 937 FieldValue newColumn = columnsToAdd.get(existingColName); 938 939 // If the column does not already exist or if the type has changed, 940 // mark the current column to be removed and keep the new column in the "to be added" list. 941 if (newColumn != null && !_isSameType(existingColumn, newColumn, dbType)) 942 { 943 columnsToRemove.put(existingColName, existingColumn); 944 } 945 else 946 { 947 // The column already exists and its type has not changed: do not touch this one. 948 columnsToAdd.remove(existingColName); 949 } 950 } 951 } 952 953 modifications.setColumnsToAdd(columnsToAdd); 954 modifications.setColumnsToRemove(columnsToRemove); 955 956 return modifications; 957 } 958 959 private Map<String, DbColumn> _getExistingColumns(Connection con, String table) throws SQLException 960 { 961 ResultSet columns = null; 962 Map<String, DbColumn> toReturn = new LinkedHashMap<>(); 963 try 964 { 965 String dbType = ConnectionHelper.getDatabaseType(con); 966 String filteredTableName = DbTypeHelper.filterName(dbType, table); 967 968 DatabaseMetaData metadata = con.getMetaData(); 969 columns = metadata.getColumns(con.getCatalog(), null, filteredTableName, null); 970 while (columns.next()) 971 { 972 String columnName = columns.getString("COLUMN_NAME"); 973 Integer sqlType = columns.getInt("DATA_TYPE"); 974 String typeName = columns.getString("TYPE_NAME"); 975 Integer colSize = columns.getInt("COLUMN_SIZE"); 976 977 DbColumn col = new DbColumn(columnName, sqlType, typeName, colSize); 978 979 toReturn.put(columnName, col); 980 } 981 } 982 catch (SQLException e) 983 { 984 __logger.error("Unable to get columns from " + table, e); 985 throw e; 986 } 987 finally 988 { 989 ConnectionHelper.cleanup(columns); 990 } 991 return toReturn; 992 } 993 994 private void _moveColumns(Connection con, Map<String, DbColumn> columnsToRemove, Map<String, DbColumn> existingColumns, String tableName) throws SQLException 995 { 996 String dbType = ConnectionHelper.getDatabaseType(con); 997 PreparedStatement stmt = null; 998 999 Set<String> existingColumnNames = existingColumns.keySet(); 1000 1001 try 1002 { 1003 for (String columnName : columnsToRemove.keySet()) 1004 { 1005 DbColumn columnToRemove = columnsToRemove.get(columnName); 1006 1007 String newName = _getNewName(con, columnName, existingColumnNames); 1008 1009 String sql = DbTypeHelper.getRenameColumnStatement(tableName, columnToRemove, newName, dbType, getSQLDatabaseTypeExtensionPoint()); 1010 1011 if (__logger.isDebugEnabled()) 1012 { 1013 __logger.debug("Moving column: " + sql); 1014 } 1015 1016 stmt = con.prepareStatement(sql); 1017 stmt.executeUpdate(); 1018 1019 // Add a column for the file name. 1020 if (columnToRemove.getSqlType() == Types.BLOB) 1021 { 1022 // Release the previous statement. 1023 ConnectionHelper.cleanup(stmt); 1024 1025 String fileNameColumn = columnName + FILE_NAME_COLUMN_SUFFIX; 1026 String newFileNameColumn = _getNewName(con, fileNameColumn, existingColumnNames); 1027 String varcharType = DbTypeHelper.getVarcharType(dbType); 1028 1029 sql = DbTypeHelper.getRenameColumnStatement(tableName, fileNameColumn, newFileNameColumn, varcharType, dbType, getSQLDatabaseTypeExtensionPoint()); 1030 1031 if (__logger.isDebugEnabled()) 1032 { 1033 __logger.debug("Altering table : " + sql.toString()); 1034 } 1035 1036 stmt = con.prepareStatement(sql.toString()); 1037 stmt.executeUpdate(); 1038 } 1039 } 1040 } 1041 catch (SQLException e) 1042 { 1043 ConnectionHelper.cleanup(stmt); 1044 throw e; 1045 } 1046 } 1047 1048 private void _addColumns(Connection con, Map<String, FieldValue> columnsToAdd, String tableName) throws SQLException 1049 { 1050 String dbType = ConnectionHelper.getDatabaseType(con); 1051 PreparedStatement stmt = null; 1052 1053 try 1054 { 1055 for (Entry<String, FieldValue> column : columnsToAdd.entrySet()) 1056 { 1057 StringBuilder sql = new StringBuilder(); 1058 1059 sql.append("ALTER TABLE ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName)); 1060 1061 int sqlType = column.getValue().getType(); 1062 1063 sql.append(" ADD ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, column.getKey())).append(" "); 1064 sql.append(_getColumnType(sqlType, dbType)); 1065 sql.append(" DEFAULT NULL"); 1066 1067 if (__logger.isDebugEnabled()) 1068 { 1069 __logger.debug("Altering table : " + sql.toString()); 1070 } 1071 1072 stmt = con.prepareStatement(sql.toString()); 1073 stmt.executeUpdate(); 1074 1075 // Add a column for the file name. 1076 if (sqlType == Types.BLOB) 1077 { 1078 // Release the previous statement. 1079 ConnectionHelper.cleanup(stmt); 1080 1081 sql.setLength(0); 1082 sql.append("ALTER TABLE ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName)); 1083 1084 String fileNameColumn = column.getKey() + FILE_NAME_COLUMN_SUFFIX; 1085 String normalizedName = DbTypeHelper.normalizeName(dbType, fileNameColumn); 1086 1087 sql.append(" ADD ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, normalizedName)).append(" "); 1088 sql.append(DbTypeHelper.getVarcharType(dbType)); 1089 sql.append(" DEFAULT NULL"); 1090 1091 if (__logger.isDebugEnabled()) 1092 { 1093 __logger.debug("Adding column: " + sql.toString()); 1094 } 1095 1096 stmt = con.prepareStatement(sql.toString()); 1097 stmt.executeUpdate(); 1098 } 1099 } 1100 } 1101 catch (SQLException e) 1102 { 1103 ConnectionHelper.cleanup(stmt); 1104 throw e; 1105 } 1106 } 1107 1108 /** 1109 * Get the new name of a column to be moved. 1110 * @param con the connection. 1111 * @param currentName the current name. 1112 * @param existingColumnNames the names of the existing columns 1113 * @return the new name. 1114 */ 1115 private String _getNewName(Connection con, String currentName, Set<String> existingColumnNames) 1116 { 1117 String dbType = ConnectionHelper.getDatabaseType(con); 1118 1119 int i = 1; 1120 String newName = DbTypeHelper.normalizeName(dbType, currentName + "_old" + i); 1121 String filteredNewName = DbTypeHelper.filterName(dbType, newName); 1122 1123 while (existingColumnNames.contains(filteredNewName)) 1124 { 1125 i++; 1126 newName = DbTypeHelper.normalizeName(dbType, currentName + "_old" + i); 1127 filteredNewName = DbTypeHelper.filterName(dbType, newName); 1128 } 1129 1130 return newName; 1131 } 1132 1133 /** 1134 * Get the SQL type corresponding to a field type. 1135 * @param fieldType the field 1136 * @return the sql type as indicated in {@link java.sql.Types}. 1137 */ 1138 public static int getFieldSqlType(FieldType fieldType) 1139 { 1140 int sqlType = Types.VARCHAR; 1141 switch (fieldType) 1142 { 1143 case TEXT: 1144 case HIDDEN: 1145 case PASSWORD: 1146 case SELECT: 1147 case RADIO: 1148 sqlType = Types.VARCHAR; 1149 break; 1150 case TEXTAREA: 1151 sqlType = Types.LONGVARCHAR; 1152 break; 1153 case CHECKBOX: 1154 sqlType = Types.BOOLEAN; 1155 break; 1156 case FILE: 1157 sqlType = Types.BLOB; 1158 break; 1159 case CAPTCHA: 1160 sqlType = Types.OTHER; 1161 break; 1162 default: 1163 break; 1164 } 1165 return sqlType; 1166 } 1167 1168 /** 1169 * Add a workflow id column to the given table 1170 * @param formId the id of the form to alter 1171 * @throws FormsException if an error occurs 1172 */ 1173 public void addWorkflowIdColumn(String formId) throws FormsException 1174 { 1175 final String tableName = TABLE_PREFIX + formId; 1176 1177 Connection con = null; 1178 PreparedStatement stmt = null; 1179 1180 try 1181 { 1182 // Connect to the database. 1183 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 1184 con = ConnectionHelper.getConnection(dataSourceId); 1185 String dbType = ConnectionHelper.getDatabaseType(con); 1186 StringBuilder sql = new StringBuilder(); 1187 1188 sql.append("ALTER TABLE ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName)); 1189 sql.append(" ADD ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, WORKFLOW_ID_FIELD)).append(" "); 1190 sql.append(DbTypeHelper.getIntegerType(dbType)); 1191 1192 if (__logger.isDebugEnabled()) 1193 { 1194 __logger.debug("Adding column: " + sql.toString()); 1195 } 1196 1197 stmt = con.prepareStatement(sql.toString()); 1198 stmt.executeUpdate(); 1199 } 1200 catch (SQLException e) 1201 { 1202 __logger.error("Error while adding the workflow id column for the table " + tableName, e); 1203 throw new FormsException("Error while adding a column to the table " + tableName, e); 1204 } 1205 finally 1206 { 1207 // Clean up connection resources 1208 ConnectionHelper.cleanup(stmt); 1209 ConnectionHelper.cleanup(con); 1210 } 1211 } 1212 1213 /** 1214 * Delete the workflow id column of the given table 1215 * @param formId the id of the form to alter 1216 * @throws FormsException if an error occurs 1217 */ 1218 public void dropWorkflowIdColumn(String formId) throws FormsException 1219 { 1220 final String tableName = TABLE_PREFIX + formId; 1221 1222 Connection con = null; 1223 PreparedStatement stmt = null; 1224 1225 try 1226 { 1227 // Connect to the database. 1228 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 1229 con = ConnectionHelper.getConnection(dataSourceId); 1230 String dbType = ConnectionHelper.getDatabaseType(con); 1231 StringBuilder sql = new StringBuilder(); 1232 1233 sql.append("ALTER TABLE ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName)); 1234 sql.append(" DROP COLUMN ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, WORKFLOW_ID_FIELD)); 1235 1236 if (__logger.isDebugEnabled()) 1237 { 1238 __logger.debug("Deleting column: " + sql.toString()); 1239 } 1240 1241 stmt = con.prepareStatement(sql.toString()); 1242 stmt.executeUpdate(); 1243 } 1244 catch (SQLException e) 1245 { 1246 __logger.error("Error while deleting the workflow id column for the table " + tableName, e); 1247 throw new FormsException("Error while deleting a column from the table " + tableName, e); 1248 } 1249 finally 1250 { 1251 // Clean up connection resources 1252 ConnectionHelper.cleanup(stmt); 1253 ConnectionHelper.cleanup(con); 1254 } 1255 } 1256 1257 /** 1258 * Set a new workflow id to the column that has the given old workflow id 1259 * @param form the form 1260 * @param entryId the id of the entry 1261 * @param newWorkflowId the new workflow id 1262 * @throws FormsException if an error occurs 1263 */ 1264 public void setWorkflowId(Form form, long entryId, long newWorkflowId) throws FormsException 1265 { 1266 final String tableName = TABLE_PREFIX + form.getId(); 1267 1268 Connection con = null; 1269 PreparedStatement stmt = null; 1270 1271 try 1272 { 1273 // Connect to the database. 1274 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 1275 con = ConnectionHelper.getConnection(dataSourceId); 1276 String dbType = ConnectionHelper.getDatabaseType(con); 1277 1278 String query = "UPDATE " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName); 1279 query += " SET " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, WORKFLOW_ID_FIELD) + " = ?"; 1280 query += " WHERE " + ID_FIELD + " = ?"; 1281 1282 stmt = con.prepareStatement(query); 1283 1284 stmt.setLong(1, newWorkflowId); 1285 stmt.setLong(2, entryId); 1286 1287 // Execute the query. 1288 stmt.executeUpdate(); 1289 } 1290 catch (SQLException e) 1291 { 1292 __logger.error("Error while resetting the workflow id for the table " + tableName, e); 1293 throw new FormsException("Error while deleting entry for table " + tableName, e); 1294 } 1295 finally 1296 { 1297 // Clean up connection resources 1298 ConnectionHelper.cleanup(stmt); 1299 ConnectionHelper.cleanup(con); 1300 } 1301 } 1302 1303 /** 1304 * Get the workflow id of the given form entry or of all the entries 1305 * @param formId the id of the form 1306 * @param entryId the entry to get the workflow id from. If null, all the workflow ids are returned 1307 * @throws FormsException if an error occurs. 1308 * @return the list of workflow ids 1309 */ 1310 public List<Integer> getWorkflowIds(String formId, Integer entryId) throws FormsException 1311 { 1312 List<Integer> workflowIds = new ArrayList<> (); 1313 1314 final String tableName = TABLE_PREFIX + formId; 1315 1316 Connection con = null; 1317 PreparedStatement stmt = null; 1318 ResultSet rs = null; 1319 1320 try 1321 { 1322 if (!hasWorkflowIdColumn(formId)) 1323 { 1324 return Collections.EMPTY_LIST; 1325 } 1326 1327 // Connect to the database. 1328 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 1329 con = ConnectionHelper.getConnection(dataSourceId); 1330 String dbType = ConnectionHelper.getDatabaseType(con); 1331 1332 String query = "SELECT " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, "workflowId") + " FROM " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName); 1333 if (entryId != null) 1334 { 1335 query += " WHERE " + ID_FIELD + " = ?"; 1336 } 1337 1338 stmt = con.prepareStatement(query); 1339 1340 if (entryId != null) 1341 { 1342 stmt.setInt(1, entryId); 1343 } 1344 1345 // Execute the query. 1346 rs = stmt.executeQuery(); 1347 1348 // Extract the result(s). 1349 while (rs.next()) 1350 { 1351 workflowIds.add(rs.getInt(1)); 1352 } 1353 } 1354 catch (SQLException e) 1355 { 1356 __logger.error("Error while getting workflow ids from the table " + tableName, e); 1357 throw new FormsException("Error while getting workflow ids from the table " + tableName, e); 1358 } 1359 finally 1360 { 1361 // Clean up connection resources 1362 ConnectionHelper.cleanup(rs); 1363 ConnectionHelper.cleanup(stmt); 1364 ConnectionHelper.cleanup(con); 1365 } 1366 1367 return workflowIds; 1368 } 1369 1370 /** 1371 * Does the given form have a workflow id column in its SQL table ? 1372 * @param formId the id of the form 1373 * @return true if the form has a workflow id column, false otherwise 1374 * @throws SQLException if a database access error occurs 1375 */ 1376 public boolean hasWorkflowIdColumn (String formId) throws SQLException 1377 { 1378 boolean hasWorkflowId = true; 1379 1380 Connection con = null; 1381 final String tableName = TABLE_PREFIX + formId; 1382 1383 try 1384 { 1385 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 1386 con = ConnectionHelper.getConnection(dataSourceId); 1387 Map<String, DbColumn> currentColumns = _getExistingColumns(con, tableName); 1388 1389 // Check if the column contains a workflow id 1390 if (!currentColumns.containsKey(WORKFLOW_ID_FIELD)) 1391 { 1392 hasWorkflowId = false; 1393 } 1394 1395 return hasWorkflowId; 1396 } 1397 finally 1398 { 1399 ConnectionHelper.cleanup(con); 1400 } 1401 } 1402 1403 /** 1404 * Delete a list of entries of a form 1405 * @param siteName The site name 1406 * @param formId The form id 1407 * @param entries The list of entries to delete 1408 * @throws ProcessingException if the given parameters are wrong 1409 * @throws FormsException if an error occurs when deleting the form submission 1410 */ 1411 @Callable 1412 public void deleteEntry(String siteName, String formId, List<Integer> entries) throws ProcessingException, FormsException 1413 { 1414 if (StringUtils.isEmpty(siteName) || StringUtils.isEmpty(formId)) 1415 { 1416 throw new ProcessingException("The site name and form ID must be provided."); 1417 } 1418 1419 Form form = getFormPropertiesManager().getForm(siteName, formId); 1420 1421 if (form == null) 1422 { 1423 throw new ProcessingException("The form of ID '" + formId + " can't be found in the site '" + siteName + "'."); 1424 } 1425 1426 final String tableName = TABLE_PREFIX + form.getId(); 1427 1428 Connection connection = null; 1429 PreparedStatement stmt = null; 1430 1431 try 1432 { 1433 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 1434 connection = ConnectionHelper.getConnection(dataSourceId); 1435 String dbType = ConnectionHelper.getDatabaseType(connection); 1436 1437 StringBuilder sb = new StringBuilder(); 1438 sb.append("DELETE FROM " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName) + " WHERE "); 1439 1440 1441 int i; 1442 int count = entries.size(); 1443 for (i = 0; i < count; i++) 1444 { 1445 if (i > 0) 1446 { 1447 sb.append(" OR "); 1448 } 1449 sb.append(ID_FIELD).append("=?"); 1450 } 1451 1452 stmt = connection.prepareStatement(sb.toString()); 1453 1454 i = 1; 1455 List<Integer> workflowIds = new ArrayList<> (); 1456 for (Integer entryId : entries) 1457 { 1458 workflowIds.addAll(getWorkflowIds(formId, entryId)); 1459 stmt.setInt(i, entryId); 1460 i++; 1461 } 1462 1463 // Delete the corresponding workflow instances and their history 1464 for (Integer workflowId : workflowIds) 1465 { 1466 getJdbcWorkflowStore().clearHistory(workflowId); 1467 getJdbcWorkflowStore().deleteInstance(workflowId); 1468 } 1469 1470 // Execute the query. 1471 stmt.executeUpdate(); 1472 } 1473 catch (SQLException e) 1474 { 1475 __logger.error("Error while deleting entry for table " + tableName, e); 1476 throw new FormsException("Error while deleting entry for table " + tableName, e); 1477 } 1478 finally 1479 { 1480 ConnectionHelper.cleanup(stmt); 1481 ConnectionHelper.cleanup(connection); 1482 } 1483 } 1484 1485 /** 1486 * Clear all entries of the forms corresponding to the given list of ids. 1487 * @param siteName the name of the site. 1488 * @param formIds the list of form ids 1489 * @throws ProcessingException if an error occurs. 1490 * @throws FormsException if an error occurs. 1491 */ 1492 @Callable 1493 public void clearEntries(String siteName, List<String> formIds) throws ProcessingException, FormsException 1494 { 1495 if (StringUtils.isEmpty(siteName) || formIds.isEmpty()) 1496 { 1497 throw new ProcessingException("The site name and form ID must be provided."); 1498 } 1499 1500 for (String formId : formIds) 1501 { 1502 Form form = getFormPropertiesManager().getForm(siteName, formId); 1503 1504 if (form == null) 1505 { 1506 throw new ProcessingException("The form of ID '" + formId + " can't be found in the site '" + siteName + "'."); 1507 } 1508 1509 final String tableName = TABLE_PREFIX + form.getId(); 1510 1511 Connection connection = null; 1512 PreparedStatement stmt = null; 1513 1514 try 1515 { 1516 1517 String dataSourceId = Config.getInstance().getValue(FORMS_POOL_CONFIG_PARAM); 1518 connection = ConnectionHelper.getConnection(dataSourceId); 1519 String dbType = ConnectionHelper.getDatabaseType(connection); 1520 1521 String request = "DELETE FROM " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName); 1522 1523 stmt = connection.prepareStatement(request); 1524 1525 List<Integer> workflowIds = getWorkflowIds(formId, null); 1526 // Delete the corresponding workflow instances and their history 1527 for (Integer workflowId : workflowIds) 1528 { 1529 getJdbcWorkflowStore().clearHistory(workflowId); 1530 getJdbcWorkflowStore().deleteInstance(workflowId); 1531 } 1532 1533 // Execute the query. 1534 stmt.executeUpdate(); 1535 } 1536 catch (SQLException e) 1537 { 1538 __logger.error("Error while deleting entry for table " + tableName, e); 1539 throw new FormsException("Error while deleting entry for table " + tableName, e); 1540 } 1541 finally 1542 { 1543 ConnectionHelper.cleanup(stmt); 1544 ConnectionHelper.cleanup(connection); 1545 } 1546 } 1547 1548 } 1549 1550 /** 1551 * Get the SELECT statement for retrieving the form entries from the database. 1552 * @param siteName The site name 1553 * @param formId The form id 1554 * @return A result map with the SQL query and the table name 1555 * @throws ProcessingException if the given parameters are wrong 1556 */ 1557 @Callable 1558 public Map<String, String> getSelectStatement(String siteName, String formId) throws ProcessingException 1559 { 1560 Map<String, String> result = new HashMap<>(); 1561 1562 try 1563 { 1564 Form form = getFormPropertiesManager().getForm(siteName, formId); 1565 1566 if (form == null) 1567 { 1568 throw new ProcessingException("The form of ID '" + formId + " can't be found in the site '" + siteName + "'."); 1569 } 1570 1571 String tableName = FormTableManager.TABLE_PREFIX + form.getId(); 1572 1573 result.put("formId", form.getId()); 1574 result.put("tableName", tableName); 1575 1576 List<String> selectClause = new ArrayList<>(); 1577 1578 selectClause.add("id as Id"); 1579 selectClause.add("creationDate as '" + getI18nUtils().translate(new I18nizableText("plugin.forms", "PLUGINS_FORMS_CHOOSE_SHOW_FORM_SUBMISSION_DATE")) + "'"); 1580 1581 Map<String, FieldValue> columns = getColumns(form); 1582 for (FieldValue column : columns.values()) 1583 { 1584 selectClause.add(column.getColumnName() + " as '" + column.getField().getLabel() + "'"); 1585 } 1586 1587 StringBuilder request = new StringBuilder(); 1588 request.append("SELECT ").append(StringUtils.join(selectClause, ", ")).append("\nFROM ").append(tableName).append(";"); 1589 1590 result.put("query", request.toString()); 1591 } 1592 catch (FormsException e) 1593 { 1594 __logger.error("An error occurred while getting the results of a form.", e); 1595 throw new ProcessingException("An error occurred while getting the results of a form.", e); 1596 } 1597 1598 return result; 1599 } 1600 1601 /** 1602 * Class storing modifications to do on columns. 1603 */ 1604 class DbColumnModifications 1605 { 1606 1607 /** The columns to remove. */ 1608 protected Map<String, DbColumn> _columnsToRemove; 1609 1610 /** The columns to remove. */ 1611 protected Map<String, FieldValue> _columnsToAdd; 1612 1613 /** 1614 * Build a DbColumnModifications object. 1615 */ 1616 public DbColumnModifications() 1617 { 1618 this(new HashMap<String, DbColumn>(), new HashMap<String, FieldValue>()); 1619 } 1620 1621 /** 1622 * Build a DbColumnModifications object. 1623 * @param columnsToRemove the columns to remove. 1624 * @param columnsToAdd the columns to add. 1625 */ 1626 public DbColumnModifications(Map<String, DbColumn> columnsToRemove, Map<String, FieldValue> columnsToAdd) 1627 { 1628 this._columnsToRemove = columnsToRemove; 1629 this._columnsToAdd = columnsToAdd; 1630 } 1631 1632 /** 1633 * Get the columnsToRemove. 1634 * @return the columnsToRemove 1635 */ 1636 public Map<String, DbColumn> getColumnsToRemove() 1637 { 1638 return _columnsToRemove; 1639 } 1640 1641 /** 1642 * Set the columnsToRemove. 1643 * @param columnsToRemove the columnsToRemove to set 1644 */ 1645 public void setColumnsToRemove(Map<String, DbColumn> columnsToRemove) 1646 { 1647 this._columnsToRemove = columnsToRemove; 1648 } 1649 1650 /** 1651 * Get the columnsToAdd. 1652 * @return the columnsToAdd 1653 */ 1654 public Map<String, FieldValue> getColumnsToAdd() 1655 { 1656 return _columnsToAdd; 1657 } 1658 1659 /** 1660 * Set the columnsToAdd. 1661 * @param columnsToAdd the columnsToAdd to set 1662 */ 1663 public void setColumnsToAdd(Map<String, FieldValue> columnsToAdd) 1664 { 1665 this._columnsToAdd = columnsToAdd; 1666 } 1667 1668 } 1669 1670 /** 1671 * Class representing a db column. 1672 */ 1673 class DbColumn 1674 { 1675 1676 /** The columns to remove. */ 1677 protected String _name; 1678 1679 /** The columns to remove. */ 1680 protected int _sqlType; 1681 1682 /** The columns to remove. */ 1683 protected String _typeName; 1684 1685 /** The columns to remove. */ 1686 protected int _columnSize; 1687 1688 /** 1689 * Build a DB column object. 1690 */ 1691 public DbColumn() 1692 { 1693 this("", 0, "", 0); 1694 } 1695 1696 /** 1697 * Build a DB column object. 1698 * @param name the column name. 1699 * @param sqlType the sql type. 1700 * @param typeName the type name. 1701 * @param columnSize the column size. 1702 */ 1703 public DbColumn(String name, int sqlType, String typeName, int columnSize) 1704 { 1705 this._name = name; 1706 this._sqlType = sqlType; 1707 this._typeName = typeName; 1708 this._columnSize = columnSize; 1709 } 1710 1711 /** 1712 * Get the name. 1713 * @return the name 1714 */ 1715 public String getName() 1716 { 1717 return _name; 1718 } 1719 1720 /** 1721 * Set the name. 1722 * @param name the name to set 1723 */ 1724 public void setName(String name) 1725 { 1726 this._name = name; 1727 } 1728 1729 /** 1730 * Get the sqlType. 1731 * @return the sqlType 1732 */ 1733 public int getSqlType() 1734 { 1735 return _sqlType; 1736 } 1737 1738 /** 1739 * Set the sqlType. 1740 * @param sqlType the sqlType to set 1741 */ 1742 public void setSqlType(int sqlType) 1743 { 1744 this._sqlType = sqlType; 1745 } 1746 1747 /** 1748 * Get the typeName. 1749 * @return the typeName 1750 */ 1751 public String getTypeName() 1752 { 1753 return _typeName; 1754 } 1755 1756 /** 1757 * Set the typeName. 1758 * @param typeName the typeName to set 1759 */ 1760 public void setTypeName(String typeName) 1761 { 1762 this._typeName = typeName; 1763 } 1764 1765 /** 1766 * Get the columnSize. 1767 * @return the columnSize 1768 */ 1769 public int getColumnSize() 1770 { 1771 return _columnSize; 1772 } 1773 1774 /** 1775 * Set the columnSize. 1776 * @param columnSize the columnSize to set 1777 */ 1778 public void setColumnSize(int columnSize) 1779 { 1780 this._columnSize = columnSize; 1781 } 1782 1783 /** 1784 * Get a type identifier corresponding to the column (i.e. TEXT, VARCHAR(255), INT(1), and so on.) 1785 * @return a type identifier corresponding to the column (i.e. TEXT, VARCHAR(255), INT(1), and so on.) 1786 */ 1787 public String getColumnTypeIdentifier() 1788 { 1789 StringBuilder buff = new StringBuilder(); 1790 1791 buff.append(_typeName); 1792 if (_typeName.equals("VARCHAR") || _typeName.equals("INT") || _typeName.equals("NUMBER")) 1793 { 1794 buff.append('(').append(_columnSize).append(')'); 1795 } 1796 1797 return buff.toString(); 1798 } 1799 1800 } 1801 1802}