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.InputStream; 019import java.net.URI; 020import java.net.URISyntaxException; 021import java.sql.Blob; 022import java.sql.Connection; 023import java.sql.DatabaseMetaData; 024import java.sql.PreparedStatement; 025import java.sql.ResultSet; 026import java.sql.SQLException; 027import java.sql.Timestamp; 028import java.sql.Types; 029import java.util.ArrayList; 030import java.util.Collections; 031import java.util.HashMap; 032import java.util.LinkedHashMap; 033import java.util.List; 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.cms.transformation.xslt.URIDecoder; 048import org.ametys.core.datasource.ConnectionHelper; 049import org.ametys.core.datasource.dbtype.SQLDatabaseTypeExtensionPoint; 050import org.ametys.core.ui.Callable; 051import org.ametys.core.util.I18nUtils; 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().getValueAsString(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().getValueAsString(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().getValueAsString(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 if (ConnectionHelper.DATABASE_POSTGRES.equals(dbType)) 368 { 369 InputStream isValue = rs.getBinaryStream(columnLabel); 370 value.setValue(isValue == null ? null : fileName); 371 } 372 else 373 { 374 Blob blobValue = rs.getBlob(columnLabel); 375 value.setValue(blobValue == null ? null : fileName); 376 } 377 378 entryValues.add(value); 379 break; 380 case Types.INTEGER: 381 Integer iValue = rs.getInt(columnLabel); 382 value = new FieldValue(columnValue); 383 value.setValue(iValue); 384 entryValues.add(value); 385 break; 386 default: 387 break; 388 } 389 } 390 391 Integer workflowId = null; 392 if (hasWorkflowIdColumn(form.getId())) 393 { 394 workflowId = rs.getInt(WORKFLOW_ID_FIELD); 395 } 396 397 if (entryIds == null || entryIds.contains(id)) 398 { 399 UserEntry entry = new UserEntry(id, creationDate, entryValues, workflowId); 400 entries.add(entry); 401 } 402 } 403 } 404 catch (SQLException e) 405 { 406 __logger.error("Error while getting entries for table " + tableName, e); 407 throw new FormsException("Error while getting entries for table " + tableName, e); 408 } 409 finally 410 { 411 ConnectionHelper.cleanup(rs); 412 ConnectionHelper.cleanup(stmt); 413 ConnectionHelper.cleanup(connection); 414 } 415 416 return entries; 417 } 418 419 /** 420 * Get the submissions query 421 * @param offset The number of results to ignore. 422 * @param length The maximum number of results to return 423 * @param tableName the name of the table 424 * @param dbType the type of the database used 425 * @return the string query for getting the submissions of a form 426 */ 427 private String _getSubmissionsQuery(int offset, int length, final String tableName, String dbType) 428 { 429 return getSQLDatabaseTypeExtensionPoint().languageLimitQuery(dbType, "SELECT * FROM " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName), Integer.toString(length), Integer.toString(offset)); 430 } 431 432 /** 433 * Get the total count of submissions for a form. 434 * @param formId the id of the form 435 * @return the total count of submissions, or -1 if an error occurs. 436 * @throws FormsException if an error occurs. 437 */ 438 public int getTotalSubmissions(String formId) throws FormsException 439 { 440 final String tableName = TABLE_PREFIX + formId; 441 442 Connection connection = null; 443 PreparedStatement stmt = null; 444 ResultSet rs = null; 445 446 try 447 { 448 449 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 450 connection = ConnectionHelper.getConnection(dataSourceId); 451 452 String dbType = ConnectionHelper.getDatabaseType(connection); 453 454 String request = "SELECT COUNT(*) FROM " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName); 455 456 stmt = connection.prepareStatement(request); 457 458 // Execute the query. 459 rs = stmt.executeQuery(); 460 461 // Extract the result. 462 if (rs.next()) 463 { 464 int count = rs.getInt(1); 465 return count; 466 } 467 } 468 catch (SQLException e) 469 { 470 __logger.error("Error while getting entries for table " + tableName, e); 471 throw new FormsException("Error while getting entries for table " + tableName, e); 472 } 473 finally 474 { 475 ConnectionHelper.cleanup(rs); 476 ConnectionHelper.cleanup(stmt); 477 ConnectionHelper.cleanup(connection); 478 } 479 480 return -1; 481 } 482 483 /** 484 * Get the forms of contents 485 * @param contentIds The ids of contents 486 * @return The forms 487 * @throws FormsException if an error occurred while getting content' form 488 * @throws URISyntaxException if an error occurred when URL encoding form's label 489 */ 490 @Callable 491 public List<Map<String, Object>> getContentForms (List<String> contentIds) throws FormsException, URISyntaxException 492 { 493 List<Map<String, Object>> result = new ArrayList<>(); 494 495 for (String contentId : contentIds) 496 { 497 Content content = getAmetysObjectResolver().resolveById(contentId); 498 499 Map<String, Object> contentforms = new HashMap<>(); 500 501 contentforms.put("id", content.getId()); 502 contentforms.put("title", URIDecoder.decode(content.getTitle())); 503 504 List<Map<String, Object>> forms2json = new ArrayList<>(); 505 506 List<Form> forms = getFormPropertiesManager().getForms(content); 507 for (Form form : forms) 508 { 509 Map<String, Object> form2json = new HashMap<>(); 510 form2json.put("id", form.getId()); 511 form2json.put("label", form.getLabel()); 512 // replace '/' character with ' ', otherwise the url wont get a match in cocoon 513 form2json.put("encodedLabel", new URI(null, null, URIDecoder.decode(form.getLabel()).replace('/', ' '), null).getRawPath()); 514 form2json.put("workflowName", StringUtils.defaultString(form.getWorkflowName(), "")); 515 516 forms2json.add(form2json); 517 } 518 519 contentforms.put("forms", forms2json); 520 521 result.add(contentforms); 522 } 523 524 return result; 525 } 526 527 /** 528 * Get the columns information of a form 529 * @param siteName The site name 530 * @param formId the identifier of form 531 * @return The columns 532 * @throws FormsException if an error occurred 533 */ 534 @Callable 535 public List<Map<String, Object>> getColumns (String siteName, String formId) throws FormsException 536 { 537 List<Map<String, Object>> columns2json = new ArrayList<>(); 538 539 Form form = getFormPropertiesManager().getForm(siteName, formId); 540 541 Map<String, FieldValue> columns = getColumns(form); 542 for (FieldValue column : columns.values()) 543 { 544 Map<String, Object> column2json = new HashMap<>(); 545 column2json.put("id", column.getColumnName()); 546 547 Field field = column.getField(); 548 column2json.put("label", field.getLabel()); 549 column2json.put("type", field.getType().toString()); 550 column2json.put("name", field.getName()); 551 column2json.put("properties", field.getProperties()); 552 553 columns2json.add(column2json); 554 } 555 556 return columns2json; 557 } 558 559 /** 560 * Remove a list of tables 561 * @param tableNames the names of the tables to delete 562 * @throws FormsException if the drop of the tables is not successful 563 */ 564 @Callable 565 public void removeTables(List<String> tableNames) throws FormsException 566 { 567 for (String tableName : tableNames) 568 { 569 if (!dropTable(tableName)) 570 { 571 throw new FormsException("An error occurred occured while removing the tables from the database."); 572 } 573 } 574 } 575 576 /** 577 * Form to tableinfo. 578 * @param form the form. 579 * @return the tableinfo. 580 */ 581 public Map<String, FieldValue> getColumns(Form form) 582 { 583 Map<String, FieldValue> columns = new LinkedHashMap<>(); 584 585 for (Field field : form.getFields()) 586 { 587 final String id = field.getId(); 588 final String name = field.getName(); 589 590 FieldValue fdValue = null; 591 592 switch (field.getType()) 593 { 594 case TEXT: 595 case HIDDEN: 596 case PASSWORD: 597 case SELECT: 598 fdValue = new FieldValue(id, Types.VARCHAR, null, field); 599 columns.put(id, fdValue); 600 break; 601 case TEXTAREA: 602 fdValue = new FieldValue(id, Types.LONGVARCHAR, null, field); 603 columns.put(id, fdValue); 604 break; 605 case RADIO: 606 if (!columns.containsKey(name)) 607 { 608 String value = field.getProperties().get("value"); 609 String label = field.getLabel(); 610 int index = 1; 611 field.getProperties().put("size", String.valueOf(index)); 612 field.getProperties().put("option-" + index + "-value", value); 613 field.getProperties().put("option-" + index + "-label", label); 614 615 field.getProperties().remove("value"); 616 617 fdValue = new FieldValue(name, Types.VARCHAR, null, field); 618 columns.put(name, fdValue); 619 } 620 else 621 { 622 // The value exists, clone it, concatenating the label. 623 if (StringUtils.isNotEmpty(field.getLabel())) 624 { 625 Field radioField = columns.get(name).getField(); 626 int index = Integer.parseInt(radioField.getProperties().get("size")); 627 index++; 628 629 String value = field.getProperties().get("value"); 630 String label = field.getLabel(); 631 632 radioField.getProperties().put("size", String.valueOf(index)); 633 radioField.getProperties().put("option-" + index + "-value", value); 634 radioField.getProperties().put("option-" + index + "-label", label); 635 636 Field dummyField = new Field(radioField.getId(), radioField.getType(), radioField.getName(), radioField.getLabel() + "/" + field.getLabel(), radioField.getProperties()); 637 columns.get(name).setField(dummyField); 638 } 639 } 640 break; 641 case CHECKBOX: 642 fdValue = new FieldValue(id, Types.BOOLEAN, null, field); 643 columns.put(id, fdValue); 644 break; 645 case FILE: 646 fdValue = new FieldValue(id, Types.BLOB, null, field); 647 columns.put(id, fdValue); 648 break; 649 case CAPTCHA: 650 break; 651 default: 652 break; 653 } 654 655 } 656 657 return columns; 658 } 659 660 /** 661 * Get the full column type corresponding to the database type (with precision). 662 * @param sqlType the sqltype value. 663 * @param dbType the database type. 664 * @return the real column type identifier (to be included in CREATE statement). 665 */ 666 protected String _getColumnType(int sqlType, String dbType) 667 { 668 switch (sqlType) 669 { 670 case Types.LONGVARCHAR: 671 return DbTypeHelper.getTextType(dbType); 672 case Types.BOOLEAN: 673 return DbTypeHelper.getBooleanType(dbType); 674 case Types.BLOB: 675 return DbTypeHelper.getBinaryType(dbType); 676 case Types.VARCHAR: 677 default: 678 return DbTypeHelper.getVarcharType(dbType); 679 } 680 } 681 682 /** 683 * Get the column type name (without precision) corresponding to the SQL type. 684 * @param sqlType the SQL type value. 685 * @param dbType the database type. 686 * @return the real column type name (without precision). 687 */ 688 protected String _getColumnTypeName(int sqlType, String dbType) 689 { 690 return StringUtils.substringBefore(_getColumnType(sqlType, dbType), "("); 691 } 692 693 /** 694 * Test if two columns have the same type. 695 * @param column the column retrieved from the database. 696 * @param newColumn the new column. 697 * @param dbType the database type. 698 * @return true if the two columns have the same type. 699 */ 700 protected boolean _isSameType(DbColumn column, FieldValue newColumn, String dbType) 701 { 702 int newColumnType = newColumn.getType(); 703 int currentColumnType = column.getSqlType(); 704 String newColumnTypeName = _getColumnTypeName(newColumnType, dbType); 705 String currentColumnTypeName = column.getTypeName(); 706 707 return currentColumnType == newColumnType || currentColumnTypeName.equals(newColumnTypeName); 708 } 709 710 private boolean _createTable(String formId, Map<String, FieldValue> columns) 711 { 712 String tableName = TABLE_PREFIX + formId; 713 714 Connection connection = null; 715 PreparedStatement stmt = null; 716 717 try 718 { 719 720 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 721 connection = ConnectionHelper.getConnection(dataSourceId); 722 723 String dbType = ConnectionHelper.getDatabaseType(connection); 724 StringBuilder sql = new StringBuilder(); 725 726 sql.append("CREATE TABLE "); 727 sql.append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName)); 728 sql.append(" ( ").append(ID_FIELD).append(" ").append(DbTypeHelper.getIdentityType(dbType)).append(" ").append(DbTypeHelper.getIdentityMarker(dbType)).append(", "); 729 sql.append(CREATION_DATE_FIELD).append(" ").append(DbTypeHelper.getDateTimeType(dbType)).append(" NOT NULL,"); 730 731 for (Map.Entry<String, FieldValue> column : columns.entrySet()) 732 { 733 int sqlType = column.getValue().getType(); 734 sql.append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, column.getKey())).append(" "); 735 sql.append(_getColumnType(sqlType, dbType)); 736 sql.append(" DEFAULT NULL,"); 737 738 // Add a column for the file name. 739 if (sqlType == Types.BLOB) 740 { 741 String fileNameColumn = column.getKey() + FILE_NAME_COLUMN_SUFFIX; 742 String normalizedName = DbTypeHelper.normalizeName(dbType, fileNameColumn); 743 744 sql.append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, normalizedName)).append(" "); 745 sql.append(DbTypeHelper.getVarcharType(dbType)); 746 sql.append(" DEFAULT NULL,"); 747 } 748 } 749 750 sql.append("PRIMARY KEY (").append(ID_FIELD).append("))"); 751 752 if (__logger.isDebugEnabled()) 753 { 754 __logger.debug("Creating table : " + sql.toString()); 755 } 756 757 stmt = connection.prepareStatement(sql.toString()); 758 stmt.executeUpdate(); 759 ConnectionHelper.cleanup(stmt); 760 761 // create sequence for oracle database 762 if (ConnectionHelper.DATABASE_ORACLE.equals(dbType)) 763 { 764 String sqlQuery = "create sequence seq_" + formId; 765 stmt = connection.prepareStatement(sqlQuery); 766 stmt.executeUpdate(); 767 } 768 769 return true; 770 } 771 catch (SQLException e) 772 { 773 __logger.error("Unable to create table " + tableName, e); 774 return false; 775 } 776 finally 777 { 778 ConnectionHelper.cleanup(stmt); 779 ConnectionHelper.cleanup(connection); 780 } 781 } 782 783 private boolean _alterTable(String formId, Map<String, FieldValue> newColumns) 784 { 785 String tableName = TABLE_PREFIX + formId; 786 787 Connection connection = null; 788 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 789 790 try 791 { 792 connection = ConnectionHelper.getConnection(dataSourceId); 793 794 // Start transaction. 795 connection.setAutoCommit(false); 796 797 // Get the existing columns. 798 Map<String, DbColumn> existingColumns = _getExistingColumns(connection, tableName); 799 800 // Compute the modifications to make. 801 DbColumnModifications modifications = _getColumnsToModify(connection, existingColumns, newColumns); 802 803 // Move the columns that were deleted or replaced. 804 _moveColumns(connection, modifications.getColumnsToRemove(), existingColumns, tableName); 805 806 // Add the new columns. 807 _addColumns(connection, modifications.getColumnsToAdd(), tableName); 808 809 // Commit the transaction. 810 connection.commit(); 811 } 812 catch (SQLException e) 813 { 814 __logger.error("Unable to alter table " + tableName, e); 815 try 816 { 817 connection.rollback(); 818 } 819 catch (SQLException sqlex) 820 { 821 // Ignore. 822 __logger.error("Error rollbacking the 'alter table' statements for table " + tableName, e); 823 } 824 return false; 825 } 826 finally 827 { 828 ConnectionHelper.cleanup(connection); 829 } 830 return true; 831 832 } 833 834 private int _checkTableStatus(String formId, Map<String, FieldValue> newColumns) 835 { 836 String tableName = TABLE_PREFIX + formId; 837 838 Connection connection = null; 839 ResultSet tables = null; 840 ResultSet rs = null; 841 842 Map<String, DbColumn> currentColumns = new HashMap<>(); 843 844 try 845 { 846 847 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 848 connection = ConnectionHelper.getConnection(dataSourceId); 849 850 String dbType = ConnectionHelper.getDatabaseType(connection); 851 852 tableName = DbTypeHelper.filterName(dbType, tableName); 853 854 DatabaseMetaData metadata = connection.getMetaData(); 855 tables = metadata.getTables(connection.getCatalog(), null, tableName, null); 856 857 if (!tables.next()) 858 { 859 return TABLE_NOT_CREATED; 860 } 861 862 currentColumns = _getExistingColumns(connection, tableName); 863 864 for (String newColumn : newColumns.keySet()) 865 { 866 String filteredColumn = DbTypeHelper.filterName(dbType, newColumn); 867 868 // Test the existence of the column. 869 if (!currentColumns.containsKey(filteredColumn)) 870 { 871 return TABLE_CREATED_BUT_NEED_UPDATE; 872 } 873 874 // Test that the column type has not changed. 875 DbColumn currentColumn = currentColumns.get(filteredColumn); 876 FieldValue newColumnFv = newColumns.get(newColumn); 877 878 if (!_isSameType(currentColumn, newColumnFv, dbType)) 879 { 880 return TABLE_CREATED_BUT_NEED_UPDATE; 881 } 882 } 883 884 return TABLE_CREATED_AND_UP_TO_DATE; 885 } 886 catch (SQLException e) 887 { 888 __logger.error("Unable to get columns from table " + tableName, e); 889 return TABLE_UNKOWN_STATUS; 890 } 891 finally 892 { 893 ConnectionHelper.cleanup(tables); 894 ConnectionHelper.cleanup(rs); 895 } 896 } 897 898 private boolean _createOrUpdateTable(String formId, final Map<String, FieldValue> columns) 899 { 900 boolean toReturn = true; 901 switch (_checkTableStatus(formId, columns)) 902 { 903 case TABLE_NOT_CREATED: 904 if (!_createTable(formId, columns)) 905 { 906 toReturn = false; 907 } 908 break; 909 case TABLE_CREATED_BUT_NEED_UPDATE: 910 if (!_alterTable(formId, columns)) 911 { 912 toReturn = false; 913 } 914 break; 915 case TABLE_CREATED_AND_UP_TO_DATE: 916 case TABLE_UNKOWN_STATUS: 917 default: 918 break; 919 } 920 return toReturn; 921 } 922 923 private DbColumnModifications _getColumnsToModify(Connection con, Map<String, DbColumn> existingColumns, Map<String, FieldValue> newColumns) 924 { 925 DbColumnModifications modifications = new DbColumnModifications(); 926 927 String dbType = ConnectionHelper.getDatabaseType(con); 928 929 Map<String, FieldValue> columnsToAdd = new LinkedHashMap<>(); 930 Map<String, DbColumn> columnsToRemove = new HashMap<>(); 931 for (Map.Entry<String, FieldValue> newColumn : newColumns.entrySet()) 932 { 933 String filteredName = DbTypeHelper.filterName(dbType, newColumn.getKey()); 934 columnsToAdd.put(filteredName, newColumn.getValue()); 935 } 936 937 if (existingColumns != null) 938 { 939 for (String existingColName : existingColumns.keySet()) 940 { 941 DbColumn existingColumn = existingColumns.get(existingColName); 942 FieldValue newColumn = columnsToAdd.get(existingColName); 943 944 // If the column does not already exist or if the type has changed, 945 // mark the current column to be removed and keep the new column in the "to be added" list. 946 if (newColumn != null && !_isSameType(existingColumn, newColumn, dbType)) 947 { 948 columnsToRemove.put(existingColName, existingColumn); 949 } 950 else 951 { 952 // The column already exists and its type has not changed: do not touch this one. 953 columnsToAdd.remove(existingColName); 954 } 955 } 956 } 957 958 modifications.setColumnsToAdd(columnsToAdd); 959 modifications.setColumnsToRemove(columnsToRemove); 960 961 return modifications; 962 } 963 964 private Map<String, DbColumn> _getExistingColumns(Connection con, String table) throws SQLException 965 { 966 ResultSet columns = null; 967 Map<String, DbColumn> toReturn = new LinkedHashMap<>(); 968 try 969 { 970 String dbType = ConnectionHelper.getDatabaseType(con); 971 String filteredTableName = DbTypeHelper.filterName(dbType, table); 972 973 DatabaseMetaData metadata = con.getMetaData(); 974 columns = metadata.getColumns(con.getCatalog(), null, filteredTableName, null); 975 while (columns.next()) 976 { 977 String columnName = columns.getString("COLUMN_NAME"); 978 Integer sqlType = columns.getInt("DATA_TYPE"); 979 String typeName = columns.getString("TYPE_NAME"); 980 Integer colSize = columns.getInt("COLUMN_SIZE"); 981 982 DbColumn col = new DbColumn(columnName, sqlType, typeName, colSize); 983 984 toReturn.put(columnName, col); 985 } 986 } 987 catch (SQLException e) 988 { 989 __logger.error("Unable to get columns from " + table, e); 990 throw e; 991 } 992 finally 993 { 994 ConnectionHelper.cleanup(columns); 995 } 996 return toReturn; 997 } 998 999 private void _moveColumns(Connection con, Map<String, DbColumn> columnsToRemove, Map<String, DbColumn> existingColumns, String tableName) throws SQLException 1000 { 1001 String dbType = ConnectionHelper.getDatabaseType(con); 1002 PreparedStatement stmt = null; 1003 1004 Set<String> existingColumnNames = existingColumns.keySet(); 1005 1006 try 1007 { 1008 for (String columnName : columnsToRemove.keySet()) 1009 { 1010 DbColumn columnToRemove = columnsToRemove.get(columnName); 1011 1012 String newName = _getNewName(con, columnName, existingColumnNames); 1013 1014 String sql = DbTypeHelper.getRenameColumnStatement(tableName, columnToRemove, newName, dbType, getSQLDatabaseTypeExtensionPoint()); 1015 1016 if (__logger.isDebugEnabled()) 1017 { 1018 __logger.debug("Moving column: " + sql); 1019 } 1020 1021 stmt = con.prepareStatement(sql); 1022 stmt.executeUpdate(); 1023 1024 // Add a column for the file name. 1025 if (columnToRemove.getSqlType() == Types.BLOB) 1026 { 1027 // Release the previous statement. 1028 ConnectionHelper.cleanup(stmt); 1029 1030 String fileNameColumn = columnName + FILE_NAME_COLUMN_SUFFIX; 1031 String newFileNameColumn = _getNewName(con, fileNameColumn, existingColumnNames); 1032 String varcharType = DbTypeHelper.getVarcharType(dbType); 1033 1034 sql = DbTypeHelper.getRenameColumnStatement(tableName, fileNameColumn, newFileNameColumn, varcharType, dbType, getSQLDatabaseTypeExtensionPoint()); 1035 1036 if (__logger.isDebugEnabled()) 1037 { 1038 __logger.debug("Altering table : " + sql.toString()); 1039 } 1040 1041 stmt = con.prepareStatement(sql.toString()); 1042 stmt.executeUpdate(); 1043 } 1044 } 1045 } 1046 catch (SQLException e) 1047 { 1048 ConnectionHelper.cleanup(stmt); 1049 throw e; 1050 } 1051 } 1052 1053 private void _addColumns(Connection con, Map<String, FieldValue> columnsToAdd, String tableName) throws SQLException 1054 { 1055 String dbType = ConnectionHelper.getDatabaseType(con); 1056 PreparedStatement stmt = null; 1057 1058 try 1059 { 1060 for (Entry<String, FieldValue> column : columnsToAdd.entrySet()) 1061 { 1062 StringBuilder sql = new StringBuilder(); 1063 1064 sql.append("ALTER TABLE ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName)); 1065 1066 int sqlType = column.getValue().getType(); 1067 1068 sql.append(" ADD ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, column.getKey())).append(" "); 1069 sql.append(_getColumnType(sqlType, dbType)); 1070 sql.append(" DEFAULT NULL"); 1071 1072 if (__logger.isDebugEnabled()) 1073 { 1074 __logger.debug("Altering table : " + sql.toString()); 1075 } 1076 1077 stmt = con.prepareStatement(sql.toString()); 1078 stmt.executeUpdate(); 1079 1080 // Add a column for the file name. 1081 if (sqlType == Types.BLOB) 1082 { 1083 // Release the previous statement. 1084 ConnectionHelper.cleanup(stmt); 1085 1086 sql.setLength(0); 1087 sql.append("ALTER TABLE ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName)); 1088 1089 String fileNameColumn = column.getKey() + FILE_NAME_COLUMN_SUFFIX; 1090 String normalizedName = DbTypeHelper.normalizeName(dbType, fileNameColumn); 1091 1092 sql.append(" ADD ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, normalizedName)).append(" "); 1093 sql.append(DbTypeHelper.getVarcharType(dbType)); 1094 sql.append(" DEFAULT NULL"); 1095 1096 if (__logger.isDebugEnabled()) 1097 { 1098 __logger.debug("Adding column: " + sql.toString()); 1099 } 1100 1101 stmt = con.prepareStatement(sql.toString()); 1102 stmt.executeUpdate(); 1103 } 1104 } 1105 } 1106 catch (SQLException e) 1107 { 1108 ConnectionHelper.cleanup(stmt); 1109 throw e; 1110 } 1111 } 1112 1113 /** 1114 * Get the new name of a column to be moved. 1115 * @param con the connection. 1116 * @param currentName the current name. 1117 * @param existingColumnNames the names of the existing columns 1118 * @return the new name. 1119 */ 1120 private String _getNewName(Connection con, String currentName, Set<String> existingColumnNames) 1121 { 1122 String dbType = ConnectionHelper.getDatabaseType(con); 1123 1124 int i = 1; 1125 String newName = DbTypeHelper.normalizeName(dbType, currentName + "_old" + i); 1126 String filteredNewName = DbTypeHelper.filterName(dbType, newName); 1127 1128 while (existingColumnNames.contains(filteredNewName)) 1129 { 1130 i++; 1131 newName = DbTypeHelper.normalizeName(dbType, currentName + "_old" + i); 1132 filteredNewName = DbTypeHelper.filterName(dbType, newName); 1133 } 1134 1135 return newName; 1136 } 1137 1138 /** 1139 * Get the SQL type corresponding to a field type. 1140 * @param fieldType the field 1141 * @return the sql type as indicated in {@link java.sql.Types}. 1142 */ 1143 public static int getFieldSqlType(FieldType fieldType) 1144 { 1145 int sqlType = Types.VARCHAR; 1146 switch (fieldType) 1147 { 1148 case TEXT: 1149 case HIDDEN: 1150 case PASSWORD: 1151 case SELECT: 1152 case RADIO: 1153 sqlType = Types.VARCHAR; 1154 break; 1155 case TEXTAREA: 1156 sqlType = Types.LONGVARCHAR; 1157 break; 1158 case CHECKBOX: 1159 sqlType = Types.BOOLEAN; 1160 break; 1161 case FILE: 1162 sqlType = Types.BLOB; 1163 break; 1164 case CAPTCHA: 1165 sqlType = Types.OTHER; 1166 break; 1167 default: 1168 break; 1169 } 1170 return sqlType; 1171 } 1172 1173 /** 1174 * Add a workflow id column to the given table 1175 * @param formId the id of the form to alter 1176 * @throws FormsException if an error occurs 1177 */ 1178 public void addWorkflowIdColumn(String formId) throws FormsException 1179 { 1180 final String tableName = TABLE_PREFIX + formId; 1181 1182 Connection con = null; 1183 PreparedStatement stmt = null; 1184 1185 try 1186 { 1187 // Connect to the database. 1188 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 1189 con = ConnectionHelper.getConnection(dataSourceId); 1190 String dbType = ConnectionHelper.getDatabaseType(con); 1191 StringBuilder sql = new StringBuilder(); 1192 1193 sql.append("ALTER TABLE ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName)); 1194 sql.append(" ADD ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, WORKFLOW_ID_FIELD)).append(" "); 1195 sql.append(DbTypeHelper.getIntegerType(dbType)); 1196 1197 if (__logger.isDebugEnabled()) 1198 { 1199 __logger.debug("Adding column: " + sql.toString()); 1200 } 1201 1202 stmt = con.prepareStatement(sql.toString()); 1203 stmt.executeUpdate(); 1204 } 1205 catch (SQLException e) 1206 { 1207 __logger.error("Error while adding the workflow id column for the table " + tableName, e); 1208 throw new FormsException("Error while adding a column to the table " + tableName, e); 1209 } 1210 finally 1211 { 1212 // Clean up connection resources 1213 ConnectionHelper.cleanup(stmt); 1214 ConnectionHelper.cleanup(con); 1215 } 1216 } 1217 1218 /** 1219 * Delete the workflow id column of the given table 1220 * @param formId the id of the form to alter 1221 * @throws FormsException if an error occurs 1222 */ 1223 public void dropWorkflowIdColumn(String formId) throws FormsException 1224 { 1225 final String tableName = TABLE_PREFIX + formId; 1226 1227 Connection con = null; 1228 PreparedStatement stmt = null; 1229 1230 try 1231 { 1232 // Connect to the database. 1233 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 1234 con = ConnectionHelper.getConnection(dataSourceId); 1235 String dbType = ConnectionHelper.getDatabaseType(con); 1236 StringBuilder sql = new StringBuilder(); 1237 1238 sql.append("ALTER TABLE ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName)); 1239 sql.append(" DROP COLUMN ").append(getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, WORKFLOW_ID_FIELD)); 1240 1241 if (__logger.isDebugEnabled()) 1242 { 1243 __logger.debug("Deleting column: " + sql.toString()); 1244 } 1245 1246 stmt = con.prepareStatement(sql.toString()); 1247 stmt.executeUpdate(); 1248 } 1249 catch (SQLException e) 1250 { 1251 __logger.error("Error while deleting the workflow id column for the table " + tableName, e); 1252 throw new FormsException("Error while deleting a column from the table " + tableName, e); 1253 } 1254 finally 1255 { 1256 // Clean up connection resources 1257 ConnectionHelper.cleanup(stmt); 1258 ConnectionHelper.cleanup(con); 1259 } 1260 } 1261 1262 /** 1263 * Set a new workflow id to the column that has the given old workflow id 1264 * @param form the form 1265 * @param entryId the id of the entry 1266 * @param newWorkflowId the new workflow id 1267 * @throws FormsException if an error occurs 1268 */ 1269 public void setWorkflowId(Form form, long entryId, long newWorkflowId) throws FormsException 1270 { 1271 final String tableName = TABLE_PREFIX + form.getId(); 1272 1273 Connection con = null; 1274 PreparedStatement stmt = null; 1275 1276 try 1277 { 1278 // Connect to the database. 1279 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 1280 con = ConnectionHelper.getConnection(dataSourceId); 1281 String dbType = ConnectionHelper.getDatabaseType(con); 1282 1283 String query = "UPDATE " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName); 1284 query += " SET " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, WORKFLOW_ID_FIELD) + " = ?"; 1285 query += " WHERE " + ID_FIELD + " = ?"; 1286 1287 stmt = con.prepareStatement(query); 1288 1289 stmt.setLong(1, newWorkflowId); 1290 stmt.setLong(2, entryId); 1291 1292 // Execute the query. 1293 stmt.executeUpdate(); 1294 } 1295 catch (SQLException e) 1296 { 1297 __logger.error("Error while resetting the workflow id for the table " + tableName, e); 1298 throw new FormsException("Error while deleting entry for table " + tableName, e); 1299 } 1300 finally 1301 { 1302 // Clean up connection resources 1303 ConnectionHelper.cleanup(stmt); 1304 ConnectionHelper.cleanup(con); 1305 } 1306 } 1307 1308 /** 1309 * Get the workflow id of the given form entry or of all the entries 1310 * @param formId the id of the form 1311 * @param entryId the entry to get the workflow id from. If null, all the workflow ids are returned 1312 * @throws FormsException if an error occurs. 1313 * @return the list of workflow ids 1314 */ 1315 public List<Integer> getWorkflowIds(String formId, Integer entryId) throws FormsException 1316 { 1317 List<Integer> workflowIds = new ArrayList<> (); 1318 1319 final String tableName = TABLE_PREFIX + formId; 1320 1321 Connection con = null; 1322 PreparedStatement stmt = null; 1323 ResultSet rs = null; 1324 1325 try 1326 { 1327 if (!hasWorkflowIdColumn(formId)) 1328 { 1329 return Collections.EMPTY_LIST; 1330 } 1331 1332 // Connect to the database. 1333 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 1334 con = ConnectionHelper.getConnection(dataSourceId); 1335 String dbType = ConnectionHelper.getDatabaseType(con); 1336 1337 String query = "SELECT " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, "workflowId") + " FROM " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName); 1338 if (entryId != null) 1339 { 1340 query += " WHERE " + ID_FIELD + " = ?"; 1341 } 1342 1343 stmt = con.prepareStatement(query); 1344 1345 if (entryId != null) 1346 { 1347 stmt.setInt(1, entryId); 1348 } 1349 1350 // Execute the query. 1351 rs = stmt.executeQuery(); 1352 1353 // Extract the result(s). 1354 while (rs.next()) 1355 { 1356 workflowIds.add(rs.getInt(1)); 1357 } 1358 } 1359 catch (SQLException e) 1360 { 1361 __logger.error("Error while getting workflow ids from the table " + tableName, e); 1362 throw new FormsException("Error while getting workflow ids from the table " + tableName, e); 1363 } 1364 finally 1365 { 1366 // Clean up connection resources 1367 ConnectionHelper.cleanup(rs); 1368 ConnectionHelper.cleanup(stmt); 1369 ConnectionHelper.cleanup(con); 1370 } 1371 1372 return workflowIds; 1373 } 1374 1375 /** 1376 * Does the given form have a workflow id column in its SQL table ? 1377 * @param formId the id of the form 1378 * @return true if the form has a workflow id column, false otherwise 1379 * @throws SQLException 1380 */ 1381 public boolean hasWorkflowIdColumn (String formId) throws SQLException 1382 { 1383 boolean hasWorkflowId = true; 1384 1385 Connection con = null; 1386 final String tableName = TABLE_PREFIX + formId; 1387 1388 try 1389 { 1390 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 1391 con = ConnectionHelper.getConnection(dataSourceId); 1392 Map<String, DbColumn> currentColumns = _getExistingColumns(con, tableName); 1393 1394 // Check if the column contains a workflow id 1395 if (!currentColumns.containsKey(WORKFLOW_ID_FIELD)) 1396 { 1397 hasWorkflowId = false; 1398 } 1399 1400 return hasWorkflowId; 1401 } 1402 finally 1403 { 1404 ConnectionHelper.cleanup(con); 1405 } 1406 } 1407 1408 /** 1409 * Delete a list of entries of a form 1410 * @param siteName The site name 1411 * @param formId The form id 1412 * @param entries The list of entries to delete 1413 * @throws ProcessingException if the given parameters are wrong 1414 * @throws FormsException if an error occurs when deleting the form submission 1415 */ 1416 @Callable 1417 public void deleteEntry(String siteName, String formId, List<Integer> entries) throws ProcessingException, FormsException 1418 { 1419 if (StringUtils.isEmpty(siteName) || StringUtils.isEmpty(formId)) 1420 { 1421 throw new ProcessingException("The site name and form ID must be provided."); 1422 } 1423 1424 Form form = getFormPropertiesManager().getForm(siteName, formId); 1425 1426 if (form == null) 1427 { 1428 throw new ProcessingException("The form of ID '" + formId + " can't be found in the site '" + siteName + "'."); 1429 } 1430 1431 final String tableName = TABLE_PREFIX + form.getId(); 1432 1433 Connection connection = null; 1434 PreparedStatement stmt = null; 1435 1436 try 1437 { 1438 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 1439 connection = ConnectionHelper.getConnection(dataSourceId); 1440 String dbType = ConnectionHelper.getDatabaseType(connection); 1441 1442 StringBuilder sb = new StringBuilder(); 1443 sb.append("DELETE FROM " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName) + " WHERE "); 1444 1445 1446 int i; 1447 int count = entries.size(); 1448 for (i = 0; i < count; i++) 1449 { 1450 if (i > 0) 1451 { 1452 sb.append(" OR "); 1453 } 1454 sb.append(ID_FIELD).append("=?"); 1455 } 1456 1457 stmt = connection.prepareStatement(sb.toString()); 1458 1459 i = 1; 1460 List<Integer> workflowIds = new ArrayList<> (); 1461 for (Integer entryId : entries) 1462 { 1463 workflowIds.addAll(getWorkflowIds(formId, entryId)); 1464 stmt.setInt(i, entryId); 1465 i++; 1466 } 1467 1468 // Delete the corresponding workflow instances and their history 1469 for (Integer workflowId : workflowIds) 1470 { 1471 getJdbcWorkflowStore().clearHistory(workflowId); 1472 getJdbcWorkflowStore().deleteInstance(workflowId); 1473 } 1474 1475 // Execute the query. 1476 stmt.executeUpdate(); 1477 } 1478 catch (SQLException e) 1479 { 1480 __logger.error("Error while deleting entry for table " + tableName, e); 1481 throw new FormsException("Error while deleting entry for table " + tableName, e); 1482 } 1483 finally 1484 { 1485 ConnectionHelper.cleanup(stmt); 1486 ConnectionHelper.cleanup(connection); 1487 } 1488 } 1489 1490 /** 1491 * Clear all entries of the forms corresponding to the given list of ids. 1492 * @param siteName the name of the site. 1493 * @param formIds the list of form ids 1494 * @throws ProcessingException if an error occurs. 1495 * @throws FormsException if an error occurs. 1496 */ 1497 @Callable 1498 public void clearEntries(String siteName, List<String> formIds) throws ProcessingException, FormsException 1499 { 1500 if (StringUtils.isEmpty(siteName) || formIds.isEmpty()) 1501 { 1502 throw new ProcessingException("The site name and form ID must be provided."); 1503 } 1504 1505 for (String formId : formIds) 1506 { 1507 Form form = getFormPropertiesManager().getForm(siteName, formId); 1508 1509 if (form == null) 1510 { 1511 throw new ProcessingException("The form of ID '" + formId + " can't be found in the site '" + siteName + "'."); 1512 } 1513 1514 final String tableName = TABLE_PREFIX + form.getId(); 1515 1516 Connection connection = null; 1517 PreparedStatement stmt = null; 1518 1519 try 1520 { 1521 1522 String dataSourceId = Config.getInstance().getValueAsString(FORMS_POOL_CONFIG_PARAM); 1523 connection = ConnectionHelper.getConnection(dataSourceId); 1524 String dbType = ConnectionHelper.getDatabaseType(connection); 1525 1526 String request = "DELETE FROM " + getSQLDatabaseTypeExtensionPoint().languageEscapeTableName(dbType, tableName); 1527 1528 stmt = connection.prepareStatement(request); 1529 1530 List<Integer> workflowIds = getWorkflowIds(formId, null); 1531 // Delete the corresponding workflow instances and their history 1532 for (Integer workflowId : workflowIds) 1533 { 1534 getJdbcWorkflowStore().clearHistory(workflowId); 1535 getJdbcWorkflowStore().deleteInstance(workflowId); 1536 } 1537 1538 // Execute the query. 1539 stmt.executeUpdate(); 1540 } 1541 catch (SQLException e) 1542 { 1543 __logger.error("Error while deleting entry for table " + tableName, e); 1544 throw new FormsException("Error while deleting entry for table " + tableName, e); 1545 } 1546 finally 1547 { 1548 ConnectionHelper.cleanup(stmt); 1549 ConnectionHelper.cleanup(connection); 1550 } 1551 } 1552 1553 } 1554 1555 /** 1556 * Get the SELECT statement for retrieving the form entries from the database. 1557 * @param siteName The site name 1558 * @param formId The form id 1559 * @return A result map with the SQL query and the table name 1560 * @throws ProcessingException if the given parameters are wrong 1561 */ 1562 @Callable 1563 public Map<String, String> getSelectStatement(String siteName, String formId) throws ProcessingException 1564 { 1565 Map<String, String> result = new HashMap<>(); 1566 1567 try 1568 { 1569 Form form = getFormPropertiesManager().getForm(siteName, formId); 1570 1571 if (form == null) 1572 { 1573 throw new ProcessingException("The form of ID '" + formId + " can't be found in the site '" + siteName + "'."); 1574 } 1575 1576 String tableName = FormTableManager.TABLE_PREFIX + form.getId(); 1577 1578 result.put("formId", form.getId()); 1579 result.put("tableName", tableName); 1580 1581 List<String> selectClause = new ArrayList<>(); 1582 1583 selectClause.add("id as Id"); 1584 selectClause.add("creationDate as '" + getI18nUtils().translate(new I18nizableText("plugin.forms", "PLUGINS_FORMS_CHOOSE_SHOW_FORM_SUBMISSION_DATE")) + "'"); 1585 1586 Map<String, FieldValue> columns = getColumns(form); 1587 for (FieldValue column : columns.values()) 1588 { 1589 selectClause.add(column.getColumnName() + " as '" + column.getField().getLabel() + "'"); 1590 } 1591 1592 StringBuilder request = new StringBuilder(); 1593 request.append("SELECT ").append(StringUtils.join(selectClause, ", ")).append("\nFROM ").append(tableName).append(";"); 1594 1595 result.put("query", request.toString()); 1596 } 1597 catch (FormsException e) 1598 { 1599 __logger.error("An error occurred while getting the results of a form.", e); 1600 throw new ProcessingException("An error occurred while getting the results of a form.", e); 1601 } 1602 1603 return result; 1604 } 1605 1606 /** 1607 * Class storing modifications to do on columns. 1608 */ 1609 class DbColumnModifications 1610 { 1611 1612 /** The columns to remove. */ 1613 protected Map<String, DbColumn> _columnsToRemove; 1614 1615 /** The columns to remove. */ 1616 protected Map<String, FieldValue> _columnsToAdd; 1617 1618 /** 1619 * Build a DbColumnModifications object. 1620 */ 1621 public DbColumnModifications() 1622 { 1623 this(new HashMap<String, DbColumn>(), new HashMap<String, FieldValue>()); 1624 } 1625 1626 /** 1627 * Build a DbColumnModifications object. 1628 * @param columnsToRemove the columns to remove. 1629 * @param columnsToAdd the columns to add. 1630 */ 1631 public DbColumnModifications(Map<String, DbColumn> columnsToRemove, Map<String, FieldValue> columnsToAdd) 1632 { 1633 this._columnsToRemove = columnsToRemove; 1634 this._columnsToAdd = columnsToAdd; 1635 } 1636 1637 /** 1638 * Get the columnsToRemove. 1639 * @return the columnsToRemove 1640 */ 1641 public Map<String, DbColumn> getColumnsToRemove() 1642 { 1643 return _columnsToRemove; 1644 } 1645 1646 /** 1647 * Set the columnsToRemove. 1648 * @param columnsToRemove the columnsToRemove to set 1649 */ 1650 public void setColumnsToRemove(Map<String, DbColumn> columnsToRemove) 1651 { 1652 this._columnsToRemove = columnsToRemove; 1653 } 1654 1655 /** 1656 * Get the columnsToAdd. 1657 * @return the columnsToAdd 1658 */ 1659 public Map<String, FieldValue> getColumnsToAdd() 1660 { 1661 return _columnsToAdd; 1662 } 1663 1664 /** 1665 * Set the columnsToAdd. 1666 * @param columnsToAdd the columnsToAdd to set 1667 */ 1668 public void setColumnsToAdd(Map<String, FieldValue> columnsToAdd) 1669 { 1670 this._columnsToAdd = columnsToAdd; 1671 } 1672 1673 } 1674 1675 /** 1676 * Class representing a db column. 1677 */ 1678 class DbColumn 1679 { 1680 1681 /** The columns to remove. */ 1682 protected String _name; 1683 1684 /** The columns to remove. */ 1685 protected int _sqlType; 1686 1687 /** The columns to remove. */ 1688 protected String _typeName; 1689 1690 /** The columns to remove. */ 1691 protected int _columnSize; 1692 1693 /** 1694 * Build a DB column object. 1695 */ 1696 public DbColumn() 1697 { 1698 this("", 0, "", 0); 1699 } 1700 1701 /** 1702 * Build a DB column object. 1703 * @param name the column name. 1704 * @param sqlType the sql type. 1705 * @param typeName the type name. 1706 * @param columnSize the column size. 1707 */ 1708 public DbColumn(String name, int sqlType, String typeName, int columnSize) 1709 { 1710 this._name = name; 1711 this._sqlType = sqlType; 1712 this._typeName = typeName; 1713 this._columnSize = columnSize; 1714 } 1715 1716 /** 1717 * Get the name. 1718 * @return the name 1719 */ 1720 public String getName() 1721 { 1722 return _name; 1723 } 1724 1725 /** 1726 * Set the name. 1727 * @param name the name to set 1728 */ 1729 public void setName(String name) 1730 { 1731 this._name = name; 1732 } 1733 1734 /** 1735 * Get the sqlType. 1736 * @return the sqlType 1737 */ 1738 public int getSqlType() 1739 { 1740 return _sqlType; 1741 } 1742 1743 /** 1744 * Set the sqlType. 1745 * @param sqlType the sqlType to set 1746 */ 1747 public void setSqlType(int sqlType) 1748 { 1749 this._sqlType = sqlType; 1750 } 1751 1752 /** 1753 * Get the typeName. 1754 * @return the typeName 1755 */ 1756 public String getTypeName() 1757 { 1758 return _typeName; 1759 } 1760 1761 /** 1762 * Set the typeName. 1763 * @param typeName the typeName to set 1764 */ 1765 public void setTypeName(String typeName) 1766 { 1767 this._typeName = typeName; 1768 } 1769 1770 /** 1771 * Get the columnSize. 1772 * @return the columnSize 1773 */ 1774 public int getColumnSize() 1775 { 1776 return _columnSize; 1777 } 1778 1779 /** 1780 * Set the columnSize. 1781 * @param columnSize the columnSize to set 1782 */ 1783 public void setColumnSize(int columnSize) 1784 { 1785 this._columnSize = columnSize; 1786 } 1787 1788 /** 1789 * Get a type identifier corresponding to the column (i.e. TEXT, VARCHAR(255), INT(1), and so on.) 1790 * @return a type identifier corresponding to the column (i.e. TEXT, VARCHAR(255), INT(1), and so on.) 1791 */ 1792 public String getColumnTypeIdentifier() 1793 { 1794 StringBuilder buff = new StringBuilder(); 1795 1796 buff.append(_typeName); 1797 if (_typeName.equals("VARCHAR") || _typeName.equals("INT") || _typeName.equals("NUMBER")) 1798 { 1799 buff.append('(').append(_columnSize).append(')'); 1800 } 1801 1802 return buff.toString(); 1803 } 1804 1805 } 1806 1807}