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 if (value.equals(properties.get(key))) 555 { 556 String index = matcher.group(1); 557 if (properties.containsKey("option-" + index + "-label")) 558 { 559 return properties.get("option-" + index + "-label"); 560 } 561 } 562 } 563 } 564 return value; 565 } 566 567 /** 568 * Extracts a form from a JCR Node. 569 * @param formNode the form node. 570 * @return the Form object. 571 * @throws RepositoryException if a repository error occurs. 572 */ 573 protected Form _extractForm(Node formNode) throws RepositoryException 574 { 575 Form form = null; 576 577 String id = _getSingleProperty(formNode, FORM_PROPERTY_ID, ""); 578 if (!StringUtils.isEmpty(id)) 579 { 580 String label = _getSingleProperty(formNode, FORM_PROPERTY_LABEL, ""); 581 String receiptFieldId = _getSingleProperty(formNode, FORM_PROPERTY_RECEIPT_FIELD_ID, ""); 582 String receiptFieldBody = _getSingleProperty(formNode, FORM_PROPERTY_RECEIPT_BODY, ""); 583 String receiptFieldSubject = _getSingleProperty(formNode, FORM_PROPERTY_RECEIPT_SUBJECT, ""); 584 String receiptFieldFromAddress = _getSingleProperty(formNode, FORM_PROPERTY_RECEIPT_FROM_ADDRESS, ""); 585 String redirectTo = _getSingleProperty(formNode, FORM_PROPERTY_REDIRECT_TO, ""); 586 Collection<String> emails = _getMultipleProperty(formNode, FORM_PROPERTY_EMAILS); 587 String workflowName = _getSingleProperty(formNode, FORM_PROPERTY_WORKFLOW_NAME, ""); 588 String limit = _getSingleProperty(formNode, FORM_PROPERTY_LIMIT, ""); 589 String remainingPlaces = _getSingleProperty(formNode, FORM_PROPERTY_REMAINING_PLACES, ""); 590 String noRemainingPlaces = _getSingleProperty(formNode, FORM_PROPERTY_NO_REMAINING_PLACES, ""); 591 592 form = new Form(); 593 594 Content content = _resolver.resolve(formNode.getParent().getParent(), false); 595 form.setContentId(content.getId()); 596 597 form.setId(id); 598 form.setLabel(label); 599 form.setReceiptFieldId(receiptFieldId); 600 form.setReceiptFieldBody(receiptFieldBody); 601 form.setReceiptFieldSubject(receiptFieldSubject); 602 form.setReceiptFieldFromAddress(receiptFieldFromAddress); 603 form.setNotificationEmails(new HashSet<>(emails)); 604 form.setRedirectTo(redirectTo); 605 form.setWorkflowName(workflowName); 606 form.setLimit(limit); 607 form.setRemainingPlaces(remainingPlaces); 608 form.setNoRemainingPlaces(noRemainingPlaces); 609 610 _extractFields(formNode, form); 611 } 612 613 return form; 614 } 615 616 /** 617 * Extracts a form from a JCR Node. 618 * @param formNode the form node. 619 * @param form the form object. 620 * @throws RepositoryException if a repository error occurs. 621 */ 622 protected void _extractFields(Node formNode, Form form) throws RepositoryException 623 { 624 NodeIterator nodes = formNode.getNodes(); 625 while (nodes.hasNext()) 626 { 627 Node node = nodes.nextNode(); 628 629 Field field = _extractField(node); 630 631 form.getFields().add(field); 632 } 633 } 634 635 /** 636 * Extracts a field from a JCR Node. 637 * @param fieldNode the field node. 638 * @return the Field object. 639 * @throws RepositoryException if a repository error occurs. 640 */ 641 protected Field _extractField(Node fieldNode) throws RepositoryException 642 { 643 Field field = null; 644 645 String id = _getSingleProperty(fieldNode, FIELD_PROPERTY_ID, ""); 646 String type = _getSingleProperty(fieldNode, FIELD_PROPERTY_TYPE, ""); 647 648 // TODO Try/catch in case of enum name not found. 649 FieldType fieldType = FieldType.valueOf(type); 650 651 if (!StringUtils.isEmpty(id) && !StringUtils.isEmpty(type)) 652 { 653 String name = _getSingleProperty(fieldNode, FIELD_PROPERTY_NAME, ""); 654 String label = _getSingleProperty(fieldNode, FIELD_PROPERTY_LABEL, ""); 655 Map<String, String> properties = _getFieldProperties(fieldNode); 656 657 field = new Field(fieldType); 658 659 field.setId(id); 660 field.setName(name); 661 field.setLabel(label); 662 field.setProperties(properties); 663 } 664 665 return field; 666 } 667 668 /** 669 * Persist the form in a repository node. 670 * @param contentNode the content node in which the form is to be stored. 671 * @param form the form object to persist. 672 * @return the newly created form node. 673 * @throws RepositoryException if a repository error occurs while filling the node. 674 */ 675 protected Node _storeForm(Node contentNode, Form form) throws RepositoryException 676 { 677 String name = form.getId(); 678 if (StringUtils.isBlank(name)) 679 { 680 name = "form"; 681 } 682 683 String nodeName = FilterNameHelper.filterName(name); 684 String notExistingNodeName = _getNotExistingNodeName(contentNode, nodeName); 685 686 Node formNode = contentNode.addNode(notExistingNodeName); 687 formNode.addMixin(JcrConstants.MIX_REFERENCEABLE); 688 689 return formNode; 690 } 691 692 /** 693 * Fill a form node. 694 * @param formNode the form node. 695 * @param form the form object. 696 * @throws RepositoryException if a repository error occurs while filling the node. 697 * @throws FormsException if a forms error occurs while filling the node. 698 */ 699 protected void _fillFormNode(Node formNode, Form form) throws RepositoryException, FormsException 700 { 701 Set<String> emails = form.getNotificationEmails(); 702 String[] emailArray = emails.toArray(new String[emails.size()]); 703 704 formNode.setProperty(FORM_PROPERTY_ID, form.getId()); 705 formNode.setProperty(FORM_PROPERTY_LABEL, form.getLabel()); 706 formNode.setProperty(FORM_PROPERTY_RECEIPT_FIELD_ID, form.getReceiptFieldId()); 707 formNode.setProperty(FORM_PROPERTY_RECEIPT_BODY, form.getReceiptFieldBody()); 708 formNode.setProperty(FORM_PROPERTY_RECEIPT_SUBJECT, form.getReceiptFieldSubject()); 709 formNode.setProperty(FORM_PROPERTY_RECEIPT_FROM_ADDRESS, form.getReceiptFieldFromAddress()); 710 formNode.setProperty(FORM_PROPERTY_EMAILS, emailArray); 711 formNode.setProperty(FORM_PROPERTY_REDIRECT_TO, form.getRedirectTo()); 712 formNode.setProperty(FORM_PROPERTY_WORKFLOW_NAME, form.getWorkflowName()); 713 formNode.setProperty(FORM_PROPERTY_REMAINING_PLACES, form.getRemainingPlaces()); 714 formNode.setProperty(FORM_PROPERTY_LIMIT, form.getLimit()); 715 formNode.setProperty(FORM_PROPERTY_NO_REMAINING_PLACES, form.getNoRemainingPlaces()); 716 } 717 718 /** 719 * Store a field node. 720 * @param formNode the form node. 721 * @param field the field. 722 * @return the newly created field node. 723 * @throws RepositoryException if a repository error occurs while filling the node. 724 * @throws FormsException if a forms error occurs while filling the node. 725 */ 726 protected Node _storeField(Node formNode, Field field) throws RepositoryException, FormsException 727 { 728 String name = field.getId(); 729 if (StringUtils.isBlank(name)) 730 { 731 name = "field"; 732 } 733 734 String nodeName = FilterNameHelper.filterName(name); 735 String notExistingNodeName = _getNotExistingNodeName(formNode, nodeName); 736 737 Node fieldNode = formNode.addNode(notExistingNodeName); 738 739 fieldNode.addMixin(JcrConstants.MIX_REFERENCEABLE); 740 741 return fieldNode; 742 } 743 744 /** 745 * Fill a field node. 746 * @param fieldNode the field node. 747 * @param field the field object. 748 * @throws RepositoryException if a repository error occurs while filling the node. 749 * @throws FormsException if a forms error occurs while filling the node. 750 */ 751 protected void _fillFieldNode(Node fieldNode, Field field) throws RepositoryException, FormsException 752 { 753 fieldNode.setProperty(FIELD_PROPERTY_ID, field.getId()); 754 fieldNode.setProperty(FIELD_PROPERTY_TYPE, field.getType().toString()); 755 fieldNode.setProperty(FIELD_PROPERTY_NAME, field.getName()); 756 fieldNode.setProperty(FIELD_PROPERTY_LABEL, field.getLabel()); 757 758 Map<String, String> fieldProperties = field.getProperties(); 759 for (String propertyName : fieldProperties.keySet()) 760 { 761 String value = fieldProperties.get(propertyName); 762 if (value != null) 763 { 764 String name = FIELD_PROPERTY_PREFIX + propertyName; 765 fieldNode.setProperty(name, value); 766 } 767 } 768 } 769 /** 770 * Update the field nodes of a form. 771 * @param form the new form object. 772 * @param formNode the node of the form to update. 773 * @throws RepositoryException if a repository error occurs while updating the fields. 774 * @throws FormsException if a forms error occurs while updating the fields. 775 */ 776 protected void _updateFields(Form form, Node formNode) throws RepositoryException, FormsException 777 { 778 Map<String, Field> fieldMap = form.getFieldMap(); 779 780 NodeIterator fieldNodes = formNode.getNodes(); 781 782 while (fieldNodes.hasNext()) 783 { 784 Node fieldNode = fieldNodes.nextNode(); 785 if (fieldNode.hasProperty(FIELD_PROPERTY_ID)) 786 { 787 String fieldId = fieldNode.getProperty(FIELD_PROPERTY_ID).getString(); 788 789 // The field still exist in the new form : update its properties. 790 if (fieldMap.containsKey(fieldId)) 791 { 792 Field field = fieldMap.get(fieldId); 793 794 _fillFieldNode(fieldNode, field); 795 796 fieldMap.remove(fieldId); 797 } 798 else 799 { 800 // The field doesn't exist anymore : delete it. 801 fieldNode.remove(); 802 } 803 } 804 } 805 806 // Now the map contains the field to add. 807 for (Map.Entry<String, Field> entry : fieldMap.entrySet()) 808 { 809 Field newField = entry.getValue(); 810 Node fieldNode = _storeField(formNode, newField); 811 _fillFieldNode(fieldNode, newField); 812 } 813 } 814 815 /** 816 * Get a name for a node which doesn't already exist in this node. 817 * @param container the container node. 818 * @param baseName the base wanted node name. 819 * @return the name, free to be taken. 820 * @throws RepositoryException if a repository error occurs. 821 */ 822 protected String _getNotExistingNodeName(Node container, String baseName) throws RepositoryException 823 { 824 String name = baseName; 825 826 int index = 2; 827 while (container.hasNode(name)) 828 { 829 name = baseName + index; 830 index++; 831 } 832 833 return name; 834 } 835 836 /** 837 * Get a single property value. 838 * @param node the JCR node. 839 * @param propertyName the name of the property to get. 840 * @param defaultValue the default value if the property does not exist. 841 * @return the single property value. 842 * @throws RepositoryException if a repository error occurs. 843 */ 844 protected String _getSingleProperty(Node node, String propertyName, String defaultValue) throws RepositoryException 845 { 846 String value = defaultValue; 847 848 if (node.hasProperty(propertyName)) 849 { 850 value = node.getProperty(propertyName).getString(); 851 } 852 853 return value; 854 } 855 856 /** 857 * Get the values of a string array property. 858 * @param node the node. 859 * @param propertyName the name of the property to get. 860 * @return the values. 861 * @throws RepositoryException if a repository error occurs. 862 */ 863 protected Collection<String> _getMultipleProperty(Node node, String propertyName) throws RepositoryException 864 { 865 List<String> values = new ArrayList<>(); 866 867 if (node.hasProperty(propertyName)) 868 { 869 Value[] propertyValues = node.getProperty(propertyName).getValues(); 870 for (Value value : propertyValues) 871 { 872 values.add(value.getString()); 873 } 874 } 875 876 return values; 877 } 878 879 /** 880 * Get additional configuration from properties. 881 * @param node the JCR node. 882 * @return the additional configuration as a Map. 883 * @throws RepositoryException if a repository error occurs. 884 */ 885 protected Map<String, String> _getFieldProperties(Node node) throws RepositoryException 886 { 887 Map<String, String> values = new HashMap<>(); 888 889 PropertyIterator propertyIt = node.getProperties(FIELD_PROPERTY_PREFIX + "*"); 890 while (propertyIt.hasNext()) 891 { 892 Property property = propertyIt.nextProperty(); 893 String propName = property.getName(); 894 String name = propName.substring(FIELD_PROPERTY_PREFIX.length(), propName.length()); 895 String value = property.getString(); 896 897 values.put(name, value); 898 } 899 900 return values; 901 } 902 903 /** 904 * Remove a form 905 * @param form The form to remove 906 * @param content The content holding the form 907 * @throws FormsException of an exception occurs when manipulating the forms' repository nodes 908 */ 909 public void remove(Form form, Content content) throws FormsException 910 { 911 Session session = null; 912 try 913 { 914 session = _repository.login(); 915 916 String id = form.getId(); 917 918 String xpathQuery = "//element(*, ametys:content)[@jcr:uuid = '" + ((JCRAmetysObject) content).getNode().getIdentifier() + "']/" + FORMS_NODE + "/*[@" + FORM_PROPERTY_ID + " = '" + id + "']"; 919 920 QueryManager queryManager = session.getWorkspace().getQueryManager(); 921 @SuppressWarnings("deprecation") 922 Query query = queryManager.createQuery(xpathQuery, Query.XPATH); 923 NodeIterator nodeIterator = query.execute().getNodes(); 924 925 if (nodeIterator.hasNext()) 926 { 927 Node formNode = nodeIterator.nextNode(); 928 formNode.remove(); 929 930 if (session.hasPendingChanges()) 931 { 932 session.save(); 933 } 934 } 935 } 936 catch (RepositoryException e) 937 { 938 throw new FormsException("Repository exception while storing the form properties.", e); 939 } 940 finally 941 { 942 if (session != null) 943 { 944 session.logout(); 945 } 946 } 947 } 948 949 /** 950 * Get or create the forms node in a content node. 951 * @param baseNode the content base node. 952 * @return the forms node. 953 * @throws RepositoryException if an error occurs. 954 */ 955 protected Node _createOrGetFormsNode(Node baseNode) throws RepositoryException 956 { 957 Node node = null; 958 if (baseNode.hasNode(FORMS_NODE)) 959 { 960 node = baseNode.getNode(FORMS_NODE); 961 } 962 else 963 { 964 node = baseNode.addNode(FORMS_NODE, "nt:unstructured"); 965 } 966 return node; 967 } 968}