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