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.jcr; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.regex.Matcher; 026import java.util.regex.Pattern; 027 028import javax.jcr.Node; 029import javax.jcr.NodeIterator; 030import javax.jcr.Property; 031import javax.jcr.PropertyIterator; 032import javax.jcr.Repository; 033import javax.jcr.RepositoryException; 034import javax.jcr.Session; 035import javax.jcr.Value; 036import javax.jcr.query.Query; 037import javax.jcr.query.QueryManager; 038 039import org.apache.avalon.framework.component.Component; 040import org.apache.avalon.framework.logger.AbstractLogEnabled; 041import org.apache.avalon.framework.service.ServiceException; 042import org.apache.avalon.framework.service.ServiceManager; 043import org.apache.avalon.framework.service.Serviceable; 044import org.apache.commons.lang.StringUtils; 045import org.apache.jackrabbit.JcrConstants; 046 047import org.ametys.cms.FilterNameHelper; 048import org.ametys.cms.repository.Content; 049import org.ametys.cms.repository.DefaultContent; 050import org.ametys.plugins.forms.Field; 051import org.ametys.plugins.forms.Field.FieldType; 052import org.ametys.plugins.forms.Form; 053import org.ametys.plugins.forms.FormsException; 054import org.ametys.plugins.repository.AmetysObjectResolver; 055import org.ametys.plugins.repository.RepositoryConstants; 056import org.ametys.plugins.repository.jcr.JCRAmetysObject; 057import org.ametys.plugins.repository.provider.AbstractRepository; 058import org.ametys.runtime.plugin.component.PluginAware; 059import org.ametys.web.repository.site.SiteManager; 060 061/** 062 * Form properties manager : stores and retrieves form properties. 063 */ 064public class FormPropertiesManager extends AbstractLogEnabled implements Serviceable, Component, PluginAware 065{ 066 /** Pattern for options value */ 067 public static final Pattern OPTION_VALUE_PATTERN = Pattern.compile("^option-([0-9]+)-value$"); 068 069 /** The avalon component ROLE. */ 070 public static final String ROLE = FormPropertiesManager.class.getName(); 071 072 /** JCR relative path to root node. */ 073 public static final String ROOT_REPO = AmetysObjectResolver.ROOT_REPO; 074 075 /** Plugins root node name. */ 076 public static final String PLUGINS_NODE = "ametys-internal:plugins"; 077 078 /** Forms node name. */ 079 public static final String FORMS_NODE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":forms"; 080 081 /** Language property */ 082 public static final String LANGUAGE_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":language"; 083 084 /** Site property */ 085 public static final String SITE_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX + ":site"; 086 087 /** "ID" property name. */ 088 public static final String FORM_PROPERTY_ID = RepositoryConstants.NAMESPACE_PREFIX + ":id"; 089 090 /** "Label" property name. */ 091 public static final String FORM_PROPERTY_LABEL = RepositoryConstants.NAMESPACE_PREFIX + ":label"; 092 093 /** "Receipt field ID" property name. */ 094 public static final String FORM_PROPERTY_RECEIPT_FIELD_ID = RepositoryConstants.NAMESPACE_PREFIX + ":receipt-field-id"; 095 096 /** "Receipt field ID" property name. */ 097 public static final String FORM_PROPERTY_RECEIPT_FROM_ADDRESS = RepositoryConstants.NAMESPACE_PREFIX + ":receipt-from-address"; 098 099 /** "Receipt field ID" property name. */ 100 public static final String FORM_PROPERTY_RECEIPT_SUBJECT = RepositoryConstants.NAMESPACE_PREFIX + ":receipt-subject"; 101 102 /** "Receipt field ID" property name. */ 103 public static final String FORM_PROPERTY_RECEIPT_BODY = RepositoryConstants.NAMESPACE_PREFIX + ":receipt-body"; 104 105 /** The uuid of the page where to redirect to */ 106 public static final String FORM_PROPERTY_REDIRECT_TO = RepositoryConstants.NAMESPACE_PREFIX + ":redirect-to"; 107 108 /** "Emails" property name. */ 109 public static final String FORM_PROPERTY_EMAILS = RepositoryConstants.NAMESPACE_PREFIX + ":notification-emails"; 110 111 /** "Workflow name" property name. */ 112 public static final String FORM_PROPERTY_WORKFLOW_NAME = RepositoryConstants.NAMESPACE_PREFIX + ":workflow-name"; 113 114 /** "ID" field property name. */ 115 public static final String FIELD_PROPERTY_ID = RepositoryConstants.NAMESPACE_PREFIX + ":id"; 116 117 /** "Type" field property name. */ 118 public static final String FIELD_PROPERTY_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":type"; 119 120 /** "Name" field property name. */ 121 public static final String FIELD_PROPERTY_NAME = RepositoryConstants.NAMESPACE_PREFIX + ":name"; 122 123 /** "Label" field property name. */ 124 public static final String FIELD_PROPERTY_LABEL = RepositoryConstants.NAMESPACE_PREFIX + ":label"; 125 126 /** Field properties prefix. */ 127 public static final String FIELD_PROPERTY_PREFIX = RepositoryConstants.NAMESPACE_PREFIX + ":property-"; 128 129 /** "Limit" property name. */ 130 public static final String FORM_PROPERTY_LIMIT = RepositoryConstants.NAMESPACE_PREFIX + ":limit"; 131 132 /** "Remaining places" property name. */ 133 public static final String FORM_PROPERTY_REMAINING_PLACES = RepositoryConstants.NAMESPACE_PREFIX + ":remaining-places"; 134 135 /** "No remaining places" property name. */ 136 public static final String FORM_PROPERTY_NO_REMAINING_PLACES = RepositoryConstants.NAMESPACE_PREFIX + ":no-remaining-places"; 137 138 /** The JCR repository. */ 139 protected Repository _repository; 140 141 /** The Site manager. */ 142 protected SiteManager _siteManager; 143 144 /** The resolver for ametys objects */ 145 protected AmetysObjectResolver _resolver; 146 147 /** The plugin name. */ 148 protected String _pluginName; 149 150 @Override 151 public void service(ServiceManager serviceManager) throws ServiceException 152 { 153 _repository = (Repository) serviceManager.lookup(AbstractRepository.ROLE); 154 _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE); 155 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 156 } 157 158 @Override 159 public void setPluginInfo(String pluginName, String featureName, String id) 160 { 161 _pluginName = pluginName; 162 } 163 164 /** 165 * Get a form from the repository. 166 * @param id the form ID. 167 * @return the Form or null if no form with this ID exists. 168 * @throws FormsException if an error occurs. 169 */ 170 public Form getForm(String id) throws FormsException 171 { 172 return getForm(null, id); 173 } 174 175 /** 176 * Get a form from the repository. 177 * @param siteName the site name. 178 * @param id the form ID. 179 * @return the Form or null if no form with this ID exists. 180 * @throws FormsException if an error occurs. 181 */ 182 public Form getForm(String siteName, String id) throws FormsException 183 { 184 Session session = null; 185 try 186 { 187 session = _repository.login(); 188 189 Form form = null; 190 191 // Build the query 192 String xpathQuery = "//element(*, ametys:content)"; 193 if (siteName != null) 194 { 195 xpathQuery += "[@" + SITE_PROPERTY + "='" + siteName + "']"; 196 } 197 xpathQuery += "/" + FORMS_NODE + "/*[@" + FORM_PROPERTY_ID + " = '" + id + "']"; 198 199 QueryManager queryManager = session.getWorkspace().getQueryManager(); 200 @SuppressWarnings("deprecation") 201 Query query = queryManager.createQuery(xpathQuery, Query.XPATH); 202 NodeIterator nodeIterator = query.execute().getNodes(); 203 204 if (nodeIterator.hasNext()) 205 { 206 Node node = nodeIterator.nextNode(); 207 208 form = _extractForm(node); 209 } 210 211 return form; 212 } 213 catch (RepositoryException e) 214 { 215 throw new FormsException("Error executing the query to find the form of id " + id, e); 216 } 217 finally 218 { 219 if (session != null) 220 { 221 session.logout(); 222 } 223 } 224 } 225 226 /** 227 * Get the most recent frozen node that contain the form of the given id 228 * @param formId the id of the form 229 * @return the list of frozen content nodes containing the form 230 * @throws FormsException if an error occurs while retrieving the forms' frozen content nodes 231 */ 232 public Node getMostRecentFormFrozenContent(String formId) throws FormsException 233 { 234 Session session = null; 235 try 236 { 237 session = _repository.login(); 238 239 String xpathQuery = "//element(" + formId + ", nt:frozenNode)/../.. order by @" + RepositoryConstants.NAMESPACE_PREFIX + ":" + DefaultContent.METADATA_MODIFIED + " descending"; 240 241 QueryManager queryManager = session.getWorkspace().getQueryManager(); 242 @SuppressWarnings("deprecation") 243 Query query = queryManager.createQuery(xpathQuery, Query.XPATH); 244 NodeIterator nodeIterator = query.execute().getNodes(); 245 246 if (nodeIterator.hasNext()) 247 { 248 return (Node) nodeIterator.next(); 249 } 250 251 return null; 252 } 253 catch (RepositoryException e) 254 { 255 getLogger().error("Error executing the query to find the frozen content nodes for the form '" + formId + "'.", e); 256 return null; 257 } 258 finally 259 { 260 if (session != null) 261 { 262 session.logout(); 263 } 264 } 265 } 266 267 /** 268 * Get the forms in a specified content. 269 * @param content the content. 270 * @return the forms as a list. 271 * @throws FormsException if an error occurs. 272 */ 273 public List<Form> getForms(Content content) throws FormsException 274 { 275 Session session = null; 276 try 277 { 278 List<Form> forms = new ArrayList<>(); 279 280 session = _repository.login(); 281 282 // FIXME API getNode should be VersionableAmetysObject 283 if (content instanceof JCRAmetysObject) 284 { 285 Node contentNode = ((JCRAmetysObject) content).getNode(); 286 287 if (contentNode.hasNode(FORMS_NODE)) 288 { 289 Node formsNode = contentNode.getNode(FORMS_NODE); 290 291 NodeIterator nodes = formsNode.getNodes(); 292 293 while (nodes.hasNext()) 294 { 295 Node node = nodes.nextNode(); 296 297 Form form = _extractForm(node); 298 299 if (form != null) 300 { 301 forms.add(form); 302 } 303 } 304 } 305 } 306 return forms; 307 } 308 catch (RepositoryException e) 309 { 310 getLogger().error("Error getting forms for a content.", e); 311 throw new FormsException("Error getting forms for a content.", e); 312 } 313 finally 314 { 315 if (session != null) 316 { 317 session.logout(); 318 } 319 } 320 } 321 322 /** 323 * Get all the contents containing at least one form of the given site with the given language 324 * @param siteName the site name. 325 * @param language the language 326 * @return the forms' list or null if none was found 327 * @throws FormsException if an error occurs. 328 */ 329 public List<Node> getFormContentNodes(String siteName, String language) throws FormsException 330 { 331 List<Node> contentNodes = new ArrayList<> (); 332 Session session = null; 333 try 334 { 335 session = _repository.login(); 336 337 String xpathQuery = "//element(*, ametys:content)[@" + SITE_PROPERTY + " = '" + siteName + "' and @" + LANGUAGE_PROPERTY + " = '" + language + "']/" + FORMS_NODE; 338 339 QueryManager queryManager = session.getWorkspace().getQueryManager(); 340 @SuppressWarnings("deprecation") 341 Query query = queryManager.createQuery(xpathQuery, Query.XPATH); 342 NodeIterator nodeIterator = query.execute().getNodes(); 343 344 while (nodeIterator.hasNext()) 345 { 346 Node formNode = nodeIterator.nextNode(); 347 contentNodes.add(formNode.getParent()); 348 } 349 350 return contentNodes; 351 } 352 catch (RepositoryException e) 353 { 354 throw new FormsException("Error executing the query to find the forms of the site '" + siteName + "' and of language '" + language + "'.", e); 355 } 356 finally 357 { 358 if (session != null) 359 { 360 session.logout(); 361 } 362 } 363 } 364 365 /** 366 * Get the content containing the form with the given id 367 * @param formId the id of the form 368 * @return the {@link Content} containing the form or <code>null</code> if not found 369 * @throws FormsException if something goes wrong when either querying the form JCR node or finding its parent {@link Content} 370 */ 371 public Content getFormContent(String formId) throws FormsException 372 { 373 Session session = null; 374 try 375 { 376 session = _repository.login(); 377 378 // Build the query 379 String xpathQuery = "//element(*, ametys:content)/" + FORMS_NODE + "/*[@" + FORM_PROPERTY_ID + " = '" + formId + "']"; 380 381 // Execute 382 QueryManager queryManager = session.getWorkspace().getQueryManager(); 383 @SuppressWarnings("deprecation") 384 Query query = queryManager.createQuery(xpathQuery, Query.XPATH); 385 NodeIterator nodeIterator = query.execute().getNodes(); 386 387 if (nodeIterator.hasNext()) 388 { 389 Node node = nodeIterator.nextNode().getParent().getParent(); 390 Content content = (Content) _resolver.resolve(node, false); 391 return content; 392 } 393 394 return null; 395 } 396 catch (RepositoryException e) 397 { 398 throw new FormsException("Error executing the query to find the content containing the form of id " + formId, e); 399 } 400 finally 401 { 402 if (session != null) 403 { 404 session.logout(); 405 } 406 } 407 408 } 409 410 411 /** 412 * Extract all the form objects from a node 413 * @param node the node 414 * @return the forms list of this node 415 * @throws FormsException if an error occurs 416 * @throws RepositoryException if an error occurs when getting the properties of a node 417 */ 418 public List<Form> getForms(Node node) throws FormsException, RepositoryException 419 { 420 List<Form> forms = new ArrayList<> (); 421 try 422 { 423 if (node.hasNode(FORMS_NODE)) 424 { 425 Node formsNode = node.getNode(FORMS_NODE); 426 if (formsNode != null) 427 { 428 NodeIterator formsNodeIterator = formsNode.getNodes(); 429 while (formsNodeIterator.hasNext()) 430 { 431 Node formNode = formsNodeIterator.nextNode(); 432 Form form = _extractForm(formNode); 433 if (form != null) 434 { 435 forms.add(form); 436 } 437 } 438 } 439 } 440 441 return forms; 442 } 443 catch (RepositoryException e) 444 { 445 throw new FormsException("Error executing the query to find the forms of the node '" + node.getName() + "' (" + node.getIdentifier() + ").", e); 446 } 447 } 448 449 /** 450 * Store the properties of a form in the repository. 451 * @param siteName the site name. 452 * @param form the form object. 453 * @param content the form content. 454 * @throws FormsException if an error occurs storing the form. 455 */ 456 public void createForm(String siteName, Form form, Content content) throws FormsException 457 { 458 try 459 { 460 // FIXME API getNode should be VersionableAmetysObject 461 if (content instanceof JCRAmetysObject) 462 { 463 Node contentNode = ((JCRAmetysObject) content).getNode(); 464 465 Node formsNode = _createOrGetFormsNode(contentNode); 466 467 Node formNode = _storeForm(formsNode, form); 468 469 _fillFormNode(formNode, form); 470 471 for (Field field : form.getFields()) 472 { 473 Node fieldNode = _storeField(formNode, field); 474 _fillFieldNode(fieldNode, field); 475 } 476 477 contentNode.getSession().save(); 478 } 479 } 480 catch (RepositoryException e) 481 { 482 throw new FormsException("Repository exception while storing the form properties.", e); 483 } 484 } 485 486 /** 487 * Update the properties of a form in the repository. 488 * @param siteName the site name. 489 * @param form the form object. 490 * @param content the form content. 491 * @throws FormsException if an error occurs storing the form. 492 */ 493 public void updateForm(String siteName, Form form, Content content) throws FormsException 494 { 495 Session session = null; 496 try 497 { 498 session = _repository.login(); 499 500 String id = form.getId(); 501 502 // FIXME API getNode should be VersionableAmetysObject 503 if (content instanceof JCRAmetysObject) 504 { 505 String xpathQuery = "//element(*, ametys:content)/" + FORMS_NODE + "/*[@" + FORM_PROPERTY_ID + " = '" + id + "']"; 506 507 QueryManager queryManager = session.getWorkspace().getQueryManager(); 508 @SuppressWarnings("deprecation") 509 Query query = queryManager.createQuery(xpathQuery, Query.XPATH); 510 NodeIterator nodeIterator = query.execute().getNodes(); 511 512 if (nodeIterator.hasNext()) 513 { 514 Node formNode = nodeIterator.nextNode(); 515 516 _fillFormNode(formNode, form); 517 518 _updateFields(form, formNode); 519 520 if (session.hasPendingChanges()) 521 { 522 session.save(); 523 } 524 } 525 } 526 } 527 catch (RepositoryException e) 528 { 529 throw new FormsException("Repository exception while storing the form properties.", e); 530 } 531 finally 532 { 533 if (session != null) 534 { 535 session.logout(); 536 } 537 } 538 } 539 540 /** 541 * Get the value for display 542 * @param field The field 543 * @param value The value 544 * @return The value to display 545 */ 546 public String getDisplayValue (Field field, String value) 547 { 548 Map<String, String> properties = field.getProperties(); 549 for (String key : properties.keySet()) 550 { 551 Matcher matcher = OPTION_VALUE_PATTERN.matcher(key); 552 if (matcher.matches()) 553 { 554 // Trim value since some rendering (including default rendering) add many leading spaces 555 if (StringUtils.trim(value).equals(StringUtils.trim(properties.get(key)))) 556 { 557 String index = matcher.group(1); 558 if (properties.containsKey("option-" + index + "-label")) 559 { 560 return properties.get("option-" + index + "-label"); 561 } 562 } 563 } 564 } 565 return value; 566 } 567 568 /** 569 * Extracts a form from a JCR Node. 570 * @param formNode the form node. 571 * @return the Form object. 572 * @throws RepositoryException if a repository error occurs. 573 */ 574 protected Form _extractForm(Node formNode) throws RepositoryException 575 { 576 Form form = null; 577 578 String id = _getSingleProperty(formNode, FORM_PROPERTY_ID, ""); 579 if (!StringUtils.isEmpty(id)) 580 { 581 String label = _getSingleProperty(formNode, FORM_PROPERTY_LABEL, ""); 582 String receiptFieldId = _getSingleProperty(formNode, FORM_PROPERTY_RECEIPT_FIELD_ID, ""); 583 String receiptFieldBody = _getSingleProperty(formNode, FORM_PROPERTY_RECEIPT_BODY, ""); 584 String receiptFieldSubject = _getSingleProperty(formNode, FORM_PROPERTY_RECEIPT_SUBJECT, ""); 585 String receiptFieldFromAddress = _getSingleProperty(formNode, FORM_PROPERTY_RECEIPT_FROM_ADDRESS, ""); 586 String redirectTo = _getSingleProperty(formNode, FORM_PROPERTY_REDIRECT_TO, ""); 587 Collection<String> emails = _getMultipleProperty(formNode, FORM_PROPERTY_EMAILS); 588 String workflowName = _getSingleProperty(formNode, FORM_PROPERTY_WORKFLOW_NAME, ""); 589 String limit = _getSingleProperty(formNode, FORM_PROPERTY_LIMIT, ""); 590 String remainingPlaces = _getSingleProperty(formNode, FORM_PROPERTY_REMAINING_PLACES, ""); 591 String noRemainingPlaces = _getSingleProperty(formNode, FORM_PROPERTY_NO_REMAINING_PLACES, ""); 592 593 form = new Form(); 594 595 Content content = _resolver.resolve(formNode.getParent().getParent(), false); 596 form.setContentId(content.getId()); 597 598 form.setId(id); 599 form.setLabel(label); 600 form.setReceiptFieldId(receiptFieldId); 601 form.setReceiptFieldBody(receiptFieldBody); 602 form.setReceiptFieldSubject(receiptFieldSubject); 603 form.setReceiptFieldFromAddress(receiptFieldFromAddress); 604 form.setNotificationEmails(new HashSet<>(emails)); 605 form.setRedirectTo(redirectTo); 606 form.setWorkflowName(workflowName); 607 form.setLimit(limit); 608 form.setRemainingPlaces(remainingPlaces); 609 form.setNoRemainingPlaces(noRemainingPlaces); 610 611 _extractFields(formNode, form); 612 } 613 614 return form; 615 } 616 617 /** 618 * Extracts a form from a JCR Node. 619 * @param formNode the form node. 620 * @param form the form object. 621 * @throws RepositoryException if a repository error occurs. 622 */ 623 protected void _extractFields(Node formNode, Form form) throws RepositoryException 624 { 625 NodeIterator nodes = formNode.getNodes(); 626 while (nodes.hasNext()) 627 { 628 Node node = nodes.nextNode(); 629 630 Field field = _extractField(node); 631 632 form.getFields().add(field); 633 } 634 } 635 636 /** 637 * Extracts a field from a JCR Node. 638 * @param fieldNode the field node. 639 * @return the Field object. 640 * @throws RepositoryException if a repository error occurs. 641 */ 642 protected Field _extractField(Node fieldNode) throws RepositoryException 643 { 644 Field field = null; 645 646 String id = _getSingleProperty(fieldNode, FIELD_PROPERTY_ID, ""); 647 String type = _getSingleProperty(fieldNode, FIELD_PROPERTY_TYPE, ""); 648 649 // TODO Try/catch in case of enum name not found. 650 FieldType fieldType = FieldType.valueOf(type); 651 652 if (!StringUtils.isEmpty(id) && !StringUtils.isEmpty(type)) 653 { 654 String name = _getSingleProperty(fieldNode, FIELD_PROPERTY_NAME, ""); 655 String label = _getSingleProperty(fieldNode, FIELD_PROPERTY_LABEL, ""); 656 Map<String, String> properties = _getFieldProperties(fieldNode); 657 658 field = new Field(fieldType); 659 660 field.setId(id); 661 field.setName(name); 662 field.setLabel(label); 663 field.setProperties(properties); 664 } 665 666 return field; 667 } 668 669 /** 670 * Persist the form in a repository node. 671 * @param contentNode the content node in which the form is to be stored. 672 * @param form the form object to persist. 673 * @return the newly created form node. 674 * @throws RepositoryException if a repository error occurs while filling the node. 675 */ 676 protected Node _storeForm(Node contentNode, Form form) throws RepositoryException 677 { 678 String name = form.getId(); 679 if (StringUtils.isBlank(name)) 680 { 681 name = "form"; 682 } 683 684 String nodeName = FilterNameHelper.filterName(name); 685 String notExistingNodeName = _getNotExistingNodeName(contentNode, nodeName); 686 687 Node formNode = contentNode.addNode(notExistingNodeName); 688 formNode.addMixin(JcrConstants.MIX_REFERENCEABLE); 689 690 return formNode; 691 } 692 693 /** 694 * Fill a form node. 695 * @param formNode the form node. 696 * @param form the form object. 697 * @throws RepositoryException if a repository error occurs while filling the node. 698 * @throws FormsException if a forms error occurs while filling the node. 699 */ 700 protected void _fillFormNode(Node formNode, Form form) throws RepositoryException, FormsException 701 { 702 Set<String> emails = form.getNotificationEmails(); 703 String[] emailArray = emails.toArray(new String[emails.size()]); 704 705 formNode.setProperty(FORM_PROPERTY_ID, form.getId()); 706 formNode.setProperty(FORM_PROPERTY_LABEL, form.getLabel()); 707 formNode.setProperty(FORM_PROPERTY_RECEIPT_FIELD_ID, form.getReceiptFieldId()); 708 formNode.setProperty(FORM_PROPERTY_RECEIPT_BODY, form.getReceiptFieldBody()); 709 formNode.setProperty(FORM_PROPERTY_RECEIPT_SUBJECT, form.getReceiptFieldSubject()); 710 formNode.setProperty(FORM_PROPERTY_RECEIPT_FROM_ADDRESS, form.getReceiptFieldFromAddress()); 711 formNode.setProperty(FORM_PROPERTY_EMAILS, emailArray); 712 formNode.setProperty(FORM_PROPERTY_REDIRECT_TO, form.getRedirectTo()); 713 formNode.setProperty(FORM_PROPERTY_WORKFLOW_NAME, form.getWorkflowName()); 714 formNode.setProperty(FORM_PROPERTY_REMAINING_PLACES, form.getRemainingPlaces()); 715 formNode.setProperty(FORM_PROPERTY_LIMIT, form.getLimit()); 716 formNode.setProperty(FORM_PROPERTY_NO_REMAINING_PLACES, form.getNoRemainingPlaces()); 717 } 718 719 /** 720 * Store a field node. 721 * @param formNode the form node. 722 * @param field the field. 723 * @return the newly created field node. 724 * @throws RepositoryException if a repository error occurs while filling the node. 725 * @throws FormsException if a forms error occurs while filling the node. 726 */ 727 protected Node _storeField(Node formNode, Field field) throws RepositoryException, FormsException 728 { 729 String name = field.getId(); 730 if (StringUtils.isBlank(name)) 731 { 732 name = "field"; 733 } 734 735 String nodeName = FilterNameHelper.filterName(name); 736 String notExistingNodeName = _getNotExistingNodeName(formNode, nodeName); 737 738 Node fieldNode = formNode.addNode(notExistingNodeName); 739 740 fieldNode.addMixin(JcrConstants.MIX_REFERENCEABLE); 741 742 return fieldNode; 743 } 744 745 /** 746 * Fill a field node. 747 * @param fieldNode the field node. 748 * @param field the field object. 749 * @throws RepositoryException if a repository error occurs while filling the node. 750 * @throws FormsException if a forms error occurs while filling the node. 751 */ 752 protected void _fillFieldNode(Node fieldNode, Field field) throws RepositoryException, FormsException 753 { 754 fieldNode.setProperty(FIELD_PROPERTY_ID, field.getId()); 755 fieldNode.setProperty(FIELD_PROPERTY_TYPE, field.getType().toString()); 756 fieldNode.setProperty(FIELD_PROPERTY_NAME, field.getName()); 757 fieldNode.setProperty(FIELD_PROPERTY_LABEL, field.getLabel()); 758 759 Map<String, String> fieldProperties = field.getProperties(); 760 for (String propertyName : fieldProperties.keySet()) 761 { 762 String value = fieldProperties.get(propertyName); 763 if (value != null) 764 { 765 String name = FIELD_PROPERTY_PREFIX + propertyName; 766 fieldNode.setProperty(name, value); 767 } 768 } 769 } 770 /** 771 * Update the field nodes of a form. 772 * @param form the new form object. 773 * @param formNode the node of the form to update. 774 * @throws RepositoryException if a repository error occurs while updating the fields. 775 * @throws FormsException if a forms error occurs while updating the fields. 776 */ 777 protected void _updateFields(Form form, Node formNode) throws RepositoryException, FormsException 778 { 779 Map<String, Field> fieldMap = form.getFieldMap(); 780 781 NodeIterator fieldNodes = formNode.getNodes(); 782 783 while (fieldNodes.hasNext()) 784 { 785 Node fieldNode = fieldNodes.nextNode(); 786 if (fieldNode.hasProperty(FIELD_PROPERTY_ID)) 787 { 788 String fieldId = fieldNode.getProperty(FIELD_PROPERTY_ID).getString(); 789 790 // The field still exist in the new form : update its properties. 791 if (fieldMap.containsKey(fieldId)) 792 { 793 Field field = fieldMap.get(fieldId); 794 795 _fillFieldNode(fieldNode, field); 796 797 fieldMap.remove(fieldId); 798 } 799 else 800 { 801 // The field doesn't exist anymore : delete it. 802 fieldNode.remove(); 803 } 804 } 805 } 806 807 // Now the map contains the field to add. 808 for (Map.Entry<String, Field> entry : fieldMap.entrySet()) 809 { 810 Field newField = entry.getValue(); 811 Node fieldNode = _storeField(formNode, newField); 812 _fillFieldNode(fieldNode, newField); 813 } 814 } 815 816 /** 817 * Get a name for a node which doesn't already exist in this node. 818 * @param container the container node. 819 * @param baseName the base wanted node name. 820 * @return the name, free to be taken. 821 * @throws RepositoryException if a repository error occurs. 822 */ 823 protected String _getNotExistingNodeName(Node container, String baseName) throws RepositoryException 824 { 825 String name = baseName; 826 827 int index = 2; 828 while (container.hasNode(name)) 829 { 830 name = baseName + index; 831 index++; 832 } 833 834 return name; 835 } 836 837 /** 838 * Get a single property value. 839 * @param node the JCR node. 840 * @param propertyName the name of the property to get. 841 * @param defaultValue the default value if the property does not exist. 842 * @return the single property value. 843 * @throws RepositoryException if a repository error occurs. 844 */ 845 protected String _getSingleProperty(Node node, String propertyName, String defaultValue) throws RepositoryException 846 { 847 String value = defaultValue; 848 849 if (node.hasProperty(propertyName)) 850 { 851 value = node.getProperty(propertyName).getString(); 852 } 853 854 return value; 855 } 856 857 /** 858 * Get the values of a string array property. 859 * @param node the node. 860 * @param propertyName the name of the property to get. 861 * @return the values. 862 * @throws RepositoryException if a repository error occurs. 863 */ 864 protected Collection<String> _getMultipleProperty(Node node, String propertyName) throws RepositoryException 865 { 866 List<String> values = new ArrayList<>(); 867 868 if (node.hasProperty(propertyName)) 869 { 870 Value[] propertyValues = node.getProperty(propertyName).getValues(); 871 for (Value value : propertyValues) 872 { 873 values.add(value.getString()); 874 } 875 } 876 877 return values; 878 } 879 880 /** 881 * Get additional configuration from properties. 882 * @param node the JCR node. 883 * @return the additional configuration as a Map. 884 * @throws RepositoryException if a repository error occurs. 885 */ 886 protected Map<String, String> _getFieldProperties(Node node) throws RepositoryException 887 { 888 Map<String, String> values = new HashMap<>(); 889 890 PropertyIterator propertyIt = node.getProperties(FIELD_PROPERTY_PREFIX + "*"); 891 while (propertyIt.hasNext()) 892 { 893 Property property = propertyIt.nextProperty(); 894 String propName = property.getName(); 895 String name = propName.substring(FIELD_PROPERTY_PREFIX.length(), propName.length()); 896 String value = property.getString(); 897 898 values.put(name, value); 899 } 900 901 return values; 902 } 903 904 /** 905 * Remove a form 906 * @param form The form to remove 907 * @param content The content holding the form 908 * @throws FormsException of an exception occurs when manipulating the forms' repository nodes 909 */ 910 public void remove(Form form, Content content) throws FormsException 911 { 912 Session session = null; 913 try 914 { 915 session = _repository.login(); 916 917 String id = form.getId(); 918 919 String xpathQuery = "//element(*, ametys:content)[@jcr:uuid = '" + ((JCRAmetysObject) content).getNode().getIdentifier() + "']/" + FORMS_NODE + "/*[@" + FORM_PROPERTY_ID + " = '" + id + "']"; 920 921 QueryManager queryManager = session.getWorkspace().getQueryManager(); 922 @SuppressWarnings("deprecation") 923 Query query = queryManager.createQuery(xpathQuery, Query.XPATH); 924 NodeIterator nodeIterator = query.execute().getNodes(); 925 926 if (nodeIterator.hasNext()) 927 { 928 Node formNode = nodeIterator.nextNode(); 929 formNode.remove(); 930 931 if (session.hasPendingChanges()) 932 { 933 session.save(); 934 } 935 } 936 } 937 catch (RepositoryException e) 938 { 939 throw new FormsException("Repository exception while storing the form properties.", e); 940 } 941 finally 942 { 943 if (session != null) 944 { 945 session.logout(); 946 } 947 } 948 } 949 950 /** 951 * Get or create the forms node in a content node. 952 * @param baseNode the content base node. 953 * @return the forms node. 954 * @throws RepositoryException if an error occurs. 955 */ 956 protected Node _createOrGetFormsNode(Node baseNode) throws RepositoryException 957 { 958 Node node = null; 959 if (baseNode.hasNode(FORMS_NODE)) 960 { 961 node = baseNode.getNode(FORMS_NODE); 962 } 963 else 964 { 965 node = baseNode.addNode(FORMS_NODE, "nt:unstructured"); 966 } 967 return node; 968 } 969}