1 18 package org.alfresco.web.ui.repo.component.property; 19 20 import java.io.IOException ; 21 import java.text.MessageFormat ; 22 import java.util.ArrayList ; 23 import java.util.List ; 24 import java.util.Map ; 25 26 import javax.faces.component.UIComponent; 27 import javax.faces.component.UIInput; 28 import javax.faces.context.FacesContext; 29 import javax.faces.context.ResponseWriter; 30 import javax.faces.el.ValueBinding; 31 import javax.faces.event.AbortProcessingException; 32 import javax.faces.event.ActionEvent; 33 import javax.faces.event.FacesEvent; 34 35 import org.alfresco.service.cmr.dictionary.AssociationDefinition; 36 import org.alfresco.service.cmr.repository.NodeRef; 37 import org.alfresco.service.cmr.repository.NodeService; 38 import org.alfresco.service.cmr.search.ResultSet; 39 import org.alfresco.service.cmr.search.SearchService; 40 import org.alfresco.service.namespace.NamespaceService; 41 import org.alfresco.service.namespace.QName; 42 import org.alfresco.web.app.Application; 43 import org.alfresco.web.bean.repository.DataDictionary; 44 import org.alfresco.web.bean.repository.Node; 45 import org.alfresco.web.bean.repository.Repository; 46 import org.alfresco.web.ui.common.Utils; 47 import org.apache.commons.logging.Log; 48 import org.apache.commons.logging.LogFactory; 49 import org.springframework.web.jsf.FacesContextUtils; 50 51 56 public abstract class BaseAssociationEditor extends UIInput 57 { 58 private final static Log logger = LogFactory.getLog(BaseAssociationEditor.class); 59 60 private final static String ACTION_SEPARATOR = ";"; 61 private final static int ACTION_NONE = -1; 62 private final static int ACTION_REMOVE = 0; 63 private final static int ACTION_SELECT = 1; 64 private final static int ACTION_ADD = 2; 65 private final static int ACTION_CHANGE = 3; 66 private final static int ACTION_CANCEL = 4; 67 private final static int ACTION_SEARCH = 5; 68 private final static int ACTION_SET = 6; 69 70 private final static String MSG_ERROR_ASSOC = "error_association"; 71 private final static String FIELD_CONTAINS = "_contains"; 72 private final static String FIELD_AVAILABLE = "_available"; 73 74 75 private final static String MSG_ADD_TO_LIST_BUTTON = "add_to_list_button"; 76 private final static String MSG_SELECT_BUTTON = "select_button"; 77 private static final String MSG_NO_SELECTED_ITEMS = "no_selected_items"; 78 private final static String MSG_SEARCH_SELECT_ITEMS = "search_select_items"; 79 private final static String MSG_SEARCH_SELECT_ITEM = "search_select_item"; 80 private final static String MSG_SELECTED_ITEMS = "selected_items"; 81 private final static String MSG_REMOVE = "remove"; 82 private final static String MSG_ADD = "add"; 83 private final static String MSG_OK = "ok"; 84 private final static String MSG_CANCEL = "cancel"; 85 private final static String MSG_SEARCH = "search"; 86 private final static String MSG_CHANGE = "change"; 87 88 protected String associationName; 89 protected String availableOptionsSize; 90 protected String selectItemMsg; 91 protected String selectItemsMsg; 92 protected String selectedItemsMsg; 93 protected String noSelectedItemsMsg; 94 protected Boolean disabled; 95 96 protected boolean showAvailable = false; 97 98 99 protected Map <String , Object > originalAssocs; 100 protected Map <String , Object > added; 101 protected Map <String , Object > removed; 102 103 104 protected List <NodeRef> availableOptions; 105 106 protected String changingAssociation; 107 protected boolean highlightedRow; 108 109 112 115 public BaseAssociationEditor() 116 { 117 setRendererType(null); 118 } 119 120 123 @SuppressWarnings ("unchecked") 124 public void restoreState(FacesContext context, Object state) 125 { 126 Object values[] = (Object [])state; 127 super.restoreState(context, values[0]); 129 this.associationName = (String )values[1]; 130 this.originalAssocs = (Map <String , Object >)values[2]; 131 this.availableOptions = (List <NodeRef>)values[3]; 132 this.availableOptionsSize = (String )values[4]; 133 this.selectItemMsg = (String )values[5]; 134 this.selectItemsMsg = (String )values[6]; 135 this.selectedItemsMsg = (String )values[7]; 136 this.changingAssociation = (String )values[8]; 137 this.disabled = (Boolean )values[9]; 138 } 139 140 143 public Object saveState(FacesContext context) 144 { 145 Object values[] = new Object [10]; 146 values[0] = super.saveState(context); 148 values[1] = this.associationName; 149 values[2] = this.originalAssocs; 150 values[3] = this.availableOptions; 151 values[4] = this.availableOptionsSize; 152 values[5] = this.selectItemMsg; 153 values[6] = this.selectItemsMsg; 154 values[7] = this.selectedItemsMsg; 155 values[8] = this.changingAssociation; 156 values[9] = this.disabled; 157 158 161 return (values); 162 } 163 164 167 public void decode(FacesContext context) 168 { 169 Map requestMap = context.getExternalContext().getRequestParameterMap(); 170 Map valuesMap = context.getExternalContext().getRequestParameterValuesMap(); 171 String fieldId = getHiddenFieldName(); 172 String value = (String )requestMap.get(fieldId); 173 174 int action = ACTION_NONE; 175 String removeId = null; 176 if (value != null && value.length() != 0) 177 { 178 int sepIdx = value.indexOf(ACTION_SEPARATOR); 180 if (sepIdx != -1) 181 { 182 action = Integer.parseInt(value.substring(0, sepIdx)); 183 removeId = value.substring(sepIdx+1); 184 } 185 else 186 { 187 action = Integer.parseInt(value); 188 } 189 } 190 191 String [] addedItems = (String [])valuesMap.get(fieldId + FIELD_AVAILABLE); 193 String contains = (String )requestMap.get(fieldId + FIELD_CONTAINS); 194 195 AssocEditorEvent event = new AssocEditorEvent(this, action, addedItems, removeId, contains); 196 queueEvent(event); 197 198 super.decode(context); 199 } 200 201 204 public void broadcast(FacesEvent event) throws AbortProcessingException 205 { 206 if (event instanceof AssocEditorEvent) 207 { 208 AssocEditorEvent assocEvent = (AssocEditorEvent)event; 209 Node node = (Node)getValue(); 210 211 switch (assocEvent.Action) 212 { 213 case ACTION_SEARCH: 214 { 215 this.showAvailable = true; 216 this.availableOptions = new ArrayList <NodeRef>(); 217 getAvailableOptions(FacesContext.getCurrentInstance(), assocEvent.Contains); 218 break; 219 } 220 case ACTION_SELECT: 221 { 222 this.showAvailable = true; 223 break; 224 } 225 case ACTION_ADD: 226 { 227 addTarget(node, assocEvent.ToAdd); 228 break; 229 } 230 case ACTION_REMOVE: 231 { 232 removeTarget(node, assocEvent.RemoveId); 233 break; 234 } 235 case ACTION_CHANGE: 236 { 237 this.changingAssociation = assocEvent.RemoveId; 238 this.showAvailable = true; 239 break; 240 } 241 case ACTION_CANCEL: 242 { 243 this.showAvailable = false; 244 break; 245 } 246 case ACTION_SET: 247 { 248 if (assocEvent.ToAdd != null && assocEvent.ToAdd.length > 0) 249 { 250 removeTarget(node, this.changingAssociation); 251 addTarget(node, assocEvent.ToAdd); 252 } 253 break; 254 } 255 } 256 } 257 else 258 { 259 super.broadcast(event); 260 } 261 } 262 263 266 public void encodeBegin(FacesContext context) throws IOException 267 { 268 if (isRendered() == false) 269 { 270 return; 271 } 272 273 this.highlightedRow = false; 275 276 ResponseWriter out = context.getResponseWriter(); 277 String clientId = getClientId(context); 278 279 NodeService nodeService = Repository.getServiceRegistry(context).getNodeService(); 281 282 AssociationDefinition assocDef = getAssociationDefinition(context); 284 if (assocDef == null) 285 { 286 logger.warn("Failed to find association definition for association '" + associationName + "'"); 287 288 String msg = MessageFormat.format(Application.getMessage(context, MSG_ERROR_ASSOC), new Object [] {this.associationName}); 290 Utils.addErrorMessage(msg); 291 } 292 else 293 { 294 String targetType = assocDef.getTargetClass().getName().toString(); 295 boolean allowMany = assocDef.isTargetMany(); 296 297 populateAssocationMaps((Node)getValue()); 298 299 if (isDisabled()) 300 { 301 renderReadOnlyAssociations(context, out, nodeService); 303 } 304 else 305 { 306 out.write("<table border='0' cellspacing='4' cellpadding='0' class='selector'>"); 308 309 if (allowMany) 310 { 311 out.write("<tr><td colspan='2'>1. "); 312 out.write(getSelectItemsMsg()); 313 out.write("</td></tr>"); 314 315 renderSearchField(context, out); 317 318 renderAvailableOptions(context, out, nodeService, targetType, allowMany); 320 321 out.write("<tr><td colspan='2'>2. <input type='submit' value='"); 323 out.write(Application.getMessage(context, MSG_ADD_TO_LIST_BUTTON)); 324 out.write("' onclick=\""); 325 out.write(generateFormSubmit(context, Integer.toString(ACTION_ADD))); 326 out.write("\"/>"); 327 328 out.write("<tr><td height='6'></td></tr>"); 330 331 out.write("<tr><td colspan='2'>"); 332 out.write(getSelectedItemsMsg()); 333 out.write("</td></tr>"); 334 335 out.write("<tr><td colspan='2'><table cellspacing='0' cellpadding='2' border='0' class='selectedItems'>"); 337 out.write("<tr><td colspan='2' class='selectedItemsHeader'>"); 338 out.write(Application.getMessage(context, "name")); 339 out.write("</td></tr>"); 340 renderExistingAssociations(context, out, nodeService, allowMany); 341 out.write("</table></td></tr>"); 342 } 343 else 344 { 345 if (this.showAvailable) 346 { 347 out.write("<tr><td colspan='2'>1. "); 348 out.write(getSelectItemMsg()); 349 out.write("</td></tr>"); 350 351 renderSearchField(context, out); 353 354 renderAvailableOptions(context, out, nodeService, targetType, allowMany); 356 357 out.write("<tr><td colspan='2' align='right'><input type='submit' value='"); 359 out.write(Application.getMessage(context, MSG_OK)); 360 out.write("' onclick=\""); 361 out.write(generateFormSubmit(context, Integer.toString(ACTION_SET))); 362 out.write("\"/> <input type='submit' value='"); 363 out.write(Application.getMessage(context, MSG_CANCEL)); 364 out.write("' onclick=\""); 365 out.write(generateFormSubmit(context, Integer.toString(ACTION_CANCEL))); 366 out.write("\"/></td></tr>"); 367 } 368 else 369 { 370 if ((allowMany == false && this.originalAssocs.size() == 0 && this.added.size() == 0) || 372 (allowMany == false && this.originalAssocs.size() == 1 && this.removed.size() == 1 && this.added.size() == 0) ) 373 { 374 out.write("<tr><td><input type='submit' value='"); 375 out.write(Application.getMessage(context, MSG_SELECT_BUTTON)); 376 out.write("' onclick=\""); 377 out.write(generateFormSubmit(context, Integer.toString(ACTION_SELECT))); 378 out.write("\"/></td></tr>"); 379 } 380 else 381 { 382 renderExistingAssociations(context, out, nodeService, allowMany); 384 } 385 } 386 } 387 388 if (logger.isDebugEnabled()) 389 { 390 logger.debug("number original = " + this.originalAssocs.size()); 391 logger.debug("number added = " + this.added.size()); 392 logger.debug("number removed = " + this.removed.size()); 393 } 394 395 out.write("</table>"); 397 } 398 } 399 } 400 401 406 public String getAssociationName() 407 { 408 ValueBinding vb = getValueBinding("associationName"); 409 if (vb != null) 410 { 411 this.associationName = (String )vb.getValue(getFacesContext()); 412 } 413 414 return this.associationName; 415 } 416 417 422 public void setAssociationName(String associationName) 423 { 424 this.associationName = associationName; 425 } 426 427 432 public boolean isDisabled() 433 { 434 if (this.disabled == null) 435 { 436 ValueBinding vb = getValueBinding("disabled"); 437 if (vb != null) 438 { 439 this.disabled = (Boolean )vb.getValue(getFacesContext()); 440 } 441 } 442 443 if (this.disabled == null) 444 { 445 this.disabled = Boolean.FALSE; 446 } 447 448 return this.disabled; 449 } 450 451 456 public void setDisabled(boolean disabled) 457 { 458 this.disabled = disabled; 459 } 460 461 467 public String getAvailableOptionsSize() 468 { 469 if (this.availableOptionsSize == null) 470 { 471 this.availableOptionsSize = "4"; 472 } 473 474 return this.availableOptionsSize; 475 } 476 477 483 public void setAvailableOptionsSize(String availableOptionsSize) 484 { 485 this.availableOptionsSize = availableOptionsSize; 486 } 487 488 494 public String getNoSelectedItemsMsg() 495 { 496 ValueBinding vb = getValueBinding("noSelectedItemsMsg"); 497 if (vb != null) 498 { 499 this.noSelectedItemsMsg = (String )vb.getValue(getFacesContext()); 500 } 501 502 if (this.noSelectedItemsMsg == null) 503 { 504 this.noSelectedItemsMsg = Application.getMessage(getFacesContext(), MSG_NO_SELECTED_ITEMS); 505 } 506 507 return this.noSelectedItemsMsg; 508 } 509 510 515 public void setNoSelectedItemsMsg(String noSelectedItemsMsg) 516 { 517 this.noSelectedItemsMsg = noSelectedItemsMsg; 518 } 519 520 526 public String getSelectedItemsMsg() 527 { 528 ValueBinding vb = getValueBinding("selectedItemsMsg"); 529 if (vb != null) 530 { 531 this.selectedItemsMsg = (String )vb.getValue(getFacesContext()); 532 } 533 534 if (this.selectedItemsMsg == null) 535 { 536 this.selectedItemsMsg = Application.getMessage(getFacesContext(), MSG_SELECTED_ITEMS); 537 } 538 539 return this.selectedItemsMsg; 540 } 541 542 547 public void setSelectedItemsMsg(String selectedItemsMsg) 548 { 549 this.selectedItemsMsg = selectedItemsMsg; 550 } 551 552 558 public String getSelectItemMsg() 559 { 560 ValueBinding vb = getValueBinding("selectItemMsg"); 561 if (vb != null) 562 { 563 this.selectItemMsg = (String )vb.getValue(getFacesContext()); 564 } 565 566 if (this.selectItemMsg == null) 567 { 568 this.selectItemMsg = Application.getMessage(getFacesContext(), MSG_SEARCH_SELECT_ITEM); 569 } 570 571 return this.selectItemMsg; 572 } 573 574 579 public void setSelectItemMsg(String selectItemMsg) 580 { 581 this.selectItemMsg = selectItemMsg; 582 } 583 584 590 public String getSelectItemsMsg() 591 { 592 ValueBinding vb = getValueBinding("selectItemsMsg"); 593 if (vb != null) 594 { 595 this.selectItemsMsg = (String )vb.getValue(getFacesContext()); 596 } 597 598 if (this.selectItemsMsg == null) 599 { 600 this.selectItemsMsg = Application.getMessage(getFacesContext(), MSG_SEARCH_SELECT_ITEMS); 601 } 602 603 return this.selectItemsMsg; 604 } 605 606 611 public void setSelectItemsMsg(String selectItemsMsg) 612 { 613 this.selectItemsMsg = selectItemsMsg; 614 } 615 616 621 protected abstract void populateAssocationMaps(Node node); 622 623 631 protected abstract void renderReadOnlyAssociations(FacesContext context, ResponseWriter out, 632 NodeService nodeService) throws IOException ; 633 634 643 protected abstract void renderExistingAssociations(FacesContext context, ResponseWriter out, 644 NodeService nodeService, boolean allowMany) throws IOException ; 645 646 652 protected abstract void removeTarget(Node node, String targetId); 653 654 660 protected abstract void addTarget(Node node, String [] toAdd); 661 662 672 protected void renderExistingAssociation(FacesContext context, ResponseWriter out, NodeService nodeService, 673 NodeRef targetRef, boolean allowMany) throws IOException 674 { 675 out.write("<tr><td class='"); 676 if (this.highlightedRow) 677 { 678 out.write("selectedItemsRowAlt"); 679 } 680 else 681 { 682 out.write("selectedItemsRow"); 683 } 684 out.write("'>"); 685 686 out.write(Repository.getDisplayPath(nodeService.getPath(targetRef))); 687 out.write("/"); 688 out.write(Repository.getNameForNode(nodeService, targetRef)); 689 out.write("</td><td class='"); 690 if (this.highlightedRow) 691 { 692 out.write("selectedItemsRowAlt"); 693 } 694 else 695 { 696 out.write("selectedItemsRow"); 697 } 698 out.write("'><a HREF='#' title='"); 699 out.write(Application.getMessage(context, MSG_REMOVE)); 700 out.write("' onclick=\""); 701 out.write(generateFormSubmit(context, ACTION_REMOVE + ACTION_SEPARATOR + targetRef.getId())); 702 out.write("\"><img SRC='"); 703 out.write(context.getExternalContext().getRequestContextPath()); 704 out.write("/images/icons/delete.gif' border='0' width='13' height='16'/></a>"); 705 706 if (allowMany == false) 707 { 708 out.write(" <a HREF='#' title='"); 709 out.write(Application.getMessage(context, MSG_CHANGE)); 710 out.write("' onclick=\""); 711 out.write(generateFormSubmit(context, ACTION_CHANGE + ACTION_SEPARATOR + targetRef.getId())); 712 out.write("\"><img SRC='"); 713 out.write(context.getExternalContext().getRequestContextPath()); 714 out.write("/images/icons/edit_icon.gif' border='0' width='12' height='16'/></a>"); 715 } 716 717 out.write("</td></tr>"); 718 719 this.highlightedRow = !this.highlightedRow; 720 } 721 722 729 protected void renderSearchField(FacesContext context, ResponseWriter out) throws IOException 730 { 731 out.write("<tr><td colspan='2'><input type='text' maxlength='1024' size='32' name='"); 733 out.write(getClientId(context) + FIELD_CONTAINS); 734 out.write("'/> <input type='submit' value='"); 735 out.write(Application.getMessage(context, MSG_SEARCH)); 736 out.write("' onclick=\""); 737 out.write(generateFormSubmit(context, Integer.toString(ACTION_SEARCH))); 738 out.write("\"/></td></tr>"); 739 } 740 741 748 protected void renderNone(FacesContext context, ResponseWriter out) throws IOException 749 { 750 out.write("<tr><td class='selectedItemsRow'>"); 751 out.write(getNoSelectedItemsMsg()); 752 out.write("</td></tr>"); 753 } 754 755 765 protected void renderAvailableOptions(FacesContext context, ResponseWriter out, NodeService nodeService, 766 String targetType, boolean allowMany) throws IOException 767 { 768 boolean itemsPresent = (this.availableOptions != null && this.availableOptions.size() > 0); 769 770 out.write("<tr><td colspan='2'><select "); 771 if (itemsPresent == false) 772 { 773 out.write("style='width:240px;' "); 775 } 776 out.write("name='"); 777 out.write(getClientId(context) + FIELD_AVAILABLE); 778 out.write("' size='"); 779 if (allowMany) 780 { 781 out.write(getAvailableOptionsSize()); 782 out.write("' multiple"); 783 } 784 else 785 { 786 out.write("1'"); 787 } 788 out.write(">"); 789 790 if (itemsPresent) 791 { 792 Node currentNode = (Node)getValue(); 793 for (NodeRef item : this.availableOptions) 794 { 795 if ((this.originalAssocs.containsKey(item.getId()) == false && this.added.containsKey(item.getId()) == false && 797 item.getId().equals(currentNode.getId()) == false) || 798 this.removed.containsKey(item.getId())) 799 { 800 out.write("<option value='"); 801 out.write(item.getId()); 802 out.write("'>"); 803 out.write(Repository.getDisplayPath(nodeService.getPath(item))); 804 out.write("/"); 805 out.write(Repository.getNameForNode(nodeService, item)); 806 out.write("</option>"); 807 } 808 } 809 } 810 811 out.write("</select></td></tr>"); 812 } 813 814 820 protected AssociationDefinition getAssociationDefinition(FacesContext context) 821 { 822 DataDictionary dd = (DataDictionary)FacesContextUtils.getRequiredWebApplicationContext( 824 context).getBean(Application.BEAN_DATA_DICTIONARY); 825 return dd.getAssociationDefinition((Node)getValue(), this.associationName); 826 } 827 828 834 protected void getAvailableOptions(FacesContext context, String contains) 835 { 836 AssociationDefinition assocDef = getAssociationDefinition(context); 837 if (assocDef != null) 838 { 839 StringBuilder query = new StringBuilder ("+TYPE:\""); 841 query.append(assocDef.getTargetClass().getName().toString()); 842 query.append("\""); 843 844 if (contains != null && contains.length() > 0) 845 { 846 String safeContains = Utils.remove(contains.trim(), "\""); 847 String nameAttr = Repository.escapeQName(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "name")); 848 849 query.append(" AND +@"); 850 query.append(nameAttr); 851 query.append(":*" + safeContains + "*"); 852 } 853 854 if (logger.isDebugEnabled()) 855 logger.debug("Query: " + query.toString()); 856 857 ResultSet results = null; 858 try 859 { 860 results = Repository.getServiceRegistry(context).getSearchService().query( 861 Repository.getStoreRef(), SearchService.LANGUAGE_LUCENE, query.toString()); 862 this.availableOptions = results.getNodeRefs(); 863 } 864 finally 865 { 866 if (results != null) 867 { 868 results.close(); 869 } 870 } 871 872 if (logger.isDebugEnabled()) 873 logger.debug("Found " + this.availableOptions.size() + " available options"); 874 } 875 } 876 877 882 private String getHiddenFieldName() 883 { 884 return getClientId(getFacesContext()); 885 } 886 887 895 private String generateFormSubmit(FacesContext context, String action) 896 { 897 return Utils.generateFormSubmit(context, this, getHiddenFieldName(), action); 898 } 899 900 903 906 public static class AssocEditorEvent extends ActionEvent 907 { 908 private static final long serialVersionUID = 7346758616063937703L; 909 910 public int Action; 911 public String [] ToAdd; 912 public String RemoveId; 913 public String Contains; 914 915 public AssocEditorEvent(UIComponent component, int action, String [] toAdd, String removeId, String contains) 916 { 917 super(component); 918 this.Action = action; 919 this.ToAdd = toAdd; 920 this.RemoveId = removeId; 921 this.Contains = contains; 922 } 923 } 924 } 925 | Popular Tags |