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