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