001/* 002 * (C) Copyright 2007 Nuxeo SA (http://nuxeo.com/) and others. 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 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * Sean Radford 019 * 020 * $Id: ComponentUtils.java 28924 2008-01-10 14:04:05Z sfermigier $ 021 */ 022 023package org.nuxeo.ecm.platform.ui.web.util; 024 025import java.io.File; 026import java.io.IOException; 027import java.io.Serializable; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.List; 031import java.util.Locale; 032import java.util.Map; 033 034import javax.el.ValueExpression; 035import javax.faces.application.FacesMessage; 036import javax.faces.component.UIComponent; 037import javax.faces.component.UISelectItems; 038import javax.faces.component.UISelectMany; 039import javax.faces.context.ExternalContext; 040import javax.faces.context.FacesContext; 041import javax.faces.model.SelectItem; 042import javax.servlet.http.HttpServletRequest; 043import javax.servlet.http.HttpServletResponse; 044 045import org.apache.commons.lang.StringUtils; 046import org.apache.commons.logging.Log; 047import org.apache.commons.logging.LogFactory; 048import org.nuxeo.common.utils.i18n.I18NUtils; 049import org.nuxeo.ecm.core.api.Blob; 050import org.nuxeo.ecm.core.api.Blobs; 051import org.nuxeo.ecm.core.api.DocumentModel; 052import org.nuxeo.ecm.core.io.download.DownloadService; 053import org.nuxeo.ecm.platform.ui.web.component.list.UIEditableList; 054import org.nuxeo.runtime.api.Framework; 055 056/** 057 * Generic component helper methods. 058 * 059 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 060 */ 061public final class ComponentUtils { 062 063 public static final String WHITE_SPACE_CHARACTER = " "; 064 065 private static final Log log = LogFactory.getLog(ComponentUtils.class); 066 067 private static final String VH_HEADER = "nuxeo-virtual-host"; 068 069 private static final String VH_PARAM = "nuxeo.virtual.host"; 070 071 public static final String FORCE_NO_CACHE_ON_MSIE = "org.nuxeo.download.force.nocache.msie"; 072 073 // Utility class. 074 private ComponentUtils() { 075 } 076 077 /** 078 * Calls a component encodeBegin/encodeChildren/encodeEnd methods. 079 */ 080 public static void encodeComponent(FacesContext context, UIComponent component) throws IOException { 081 component.encodeBegin(context); 082 component.encodeChildren(context); 083 component.encodeEnd(context); 084 } 085 086 /** 087 * Helper method meant to be called in the component constructor. 088 * <p> 089 * When adding sub components dynamically, the tree fetching could be a problem so all possible sub components must 090 * be added. 091 * <p> 092 * Since 6.0, does not mark component as not rendered anymore, calls 093 * {@link #hookSubComponent(FacesContext, UIComponent, UIComponent, String)} directly. 094 * 095 * @param parent 096 * @param child 097 * @param facetName facet name to put the child in. 098 */ 099 public static void initiateSubComponent(UIComponent parent, String facetName, UIComponent child) { 100 parent.getFacets().put(facetName, child); 101 hookSubComponent(null, parent, child, facetName); 102 } 103 104 /** 105 * Add a sub component to a UI component. 106 * <p> 107 * Since 6.0, does not the set the component as rendered anymore. 108 * 109 * @param context 110 * @param parent 111 * @param child 112 * @param defaultChildId 113 * @return child comp 114 */ 115 public static UIComponent hookSubComponent(FacesContext context, UIComponent parent, UIComponent child, 116 String defaultChildId) { 117 // build a valid id using the parent id so that it's found everytime. 118 String childId = child.getId(); 119 if (defaultChildId != null) { 120 // override with default 121 childId = defaultChildId; 122 } 123 // make sure it's set 124 if (childId == null) { 125 childId = context.getViewRoot().createUniqueId(); 126 } 127 // reset client id 128 child.setId(childId); 129 child.setParent(parent); 130 return child; 131 } 132 133 /** 134 * Copies attributes and value expressions with given name from parent component to child component. 135 */ 136 public static void copyValues(UIComponent parent, UIComponent child, String[] valueNames) { 137 Map<String, Object> parentAttributes = parent.getAttributes(); 138 Map<String, Object> childAttributes = child.getAttributes(); 139 for (String name : valueNames) { 140 // attributes 141 if (parentAttributes.containsKey(name)) { 142 childAttributes.put(name, parentAttributes.get(name)); 143 } 144 // value expressions 145 ValueExpression ve = parent.getValueExpression(name); 146 if (ve != null) { 147 child.setValueExpression(name, ve); 148 } 149 } 150 } 151 152 public static void copyLinkValues(UIComponent parent, UIComponent child) { 153 String[] valueNames = { "accesskey", "charset", "coords", "dir", "disabled", "hreflang", "lang", "onblur", 154 "onclick", "ondblclick", "onfocus", "onkeydown", "onkeypress", "onkeyup", "onmousedown", "onmousemove", 155 "onmouseout", "onmouseover", "onmouseup", "rel", "rev", "shape", "style", "styleClass", "tabindex", 156 "target", "title", "type" }; 157 copyValues(parent, child, valueNames); 158 } 159 160 public static Object getAttributeValue(UIComponent component, String attributeName, Object defaultValue) { 161 Object value = component.getAttributes().get(attributeName); 162 if (value == null) { 163 value = defaultValue; 164 } 165 return value; 166 } 167 168 public static Object getAttributeOrExpressionValue(FacesContext context, UIComponent component, 169 String attributeName, Object defaultValue) { 170 Object value = component.getAttributes().get(attributeName); 171 if (value == null) { 172 ValueExpression schemaExpr = component.getValueExpression(attributeName); 173 value = schemaExpr.getValue(context.getELContext()); 174 } 175 if (value == null) { 176 value = defaultValue; 177 } 178 return value; 179 } 180 181 /** 182 * Downloads a blob and sends it to the requesting user, in the JSF current context. 183 * 184 * @param doc the document, if available 185 * @param xpath the blob's xpath or blobholder index, if available 186 * @param blob the blob, if already fetched 187 * @param filename the filename to use 188 * @param reason the download reason 189 * @since 7.3 190 */ 191 public static void download(DocumentModel doc, String xpath, Blob blob, String filename, String reason) { 192 download(doc, xpath, blob, filename, reason, null); 193 } 194 195 /** 196 * Downloads a blob and sends it to the requesting user, in the JSF current context. 197 * 198 * @param doc the document, if available 199 * @param xpath the blob's xpath or blobholder index, if available 200 * @param blob the blob, if already fetched 201 * @param filename the filename to use 202 * @param reason the download reason 203 * @param extendedInfos an optional map of extended informations to log 204 * @since 7.3 205 */ 206 public static void download(DocumentModel doc, String xpath, Blob blob, String filename, String reason, 207 Map<String, Serializable> extendedInfos) { 208 FacesContext facesContext = FacesContext.getCurrentInstance(); 209 if (facesContext.getResponseComplete()) { 210 // nothing can be written, an error was probably already sent. don't bother 211 log.debug("Cannot send " + filename + ", response already complete"); 212 return; 213 } 214 if (facesContext.getPartialViewContext().isAjaxRequest()) { 215 // do not perform download in an ajax request 216 return; 217 } 218 ExternalContext externalContext = facesContext.getExternalContext(); 219 HttpServletRequest request = (HttpServletRequest) externalContext.getRequest(); 220 HttpServletResponse response = (HttpServletResponse) externalContext.getResponse(); 221 try { 222 DownloadService downloadService = Framework.getService(DownloadService.class); 223 downloadService.downloadBlob(request, response, doc, xpath, blob, filename, reason, extendedInfos); 224 } catch (IOException e) { 225 log.error("Error while downloading the file: " + filename, e); 226 } finally { 227 facesContext.responseComplete(); 228 } 229 } 230 231 public static String downloadFile(File file, String filename, String reason) throws IOException { 232 Blob blob = Blobs.createBlob(file); 233 download(null, null, blob, filename, reason); 234 return null; 235 } 236 237 /** 238 * @deprecated since 7.3, use {@link #downloadFile(Blob, String)} instead 239 */ 240 @Deprecated 241 public static String download(FacesContext faces, Blob blob, String filename) { 242 download(null, null, blob, filename, "download"); 243 return null; 244 } 245 246 /** 247 * @deprecated since 7.3, use {@link #downloadFile(File, String)} instead 248 */ 249 @Deprecated 250 public static String downloadFile(FacesContext faces, String filename, File file) throws IOException { 251 return downloadFile(file, filename, null); 252 } 253 254 protected static boolean forceNoCacheOnMSIE() { 255 // see NXP-7759 256 return Framework.isBooleanPropertyTrue(FORCE_NO_CACHE_ON_MSIE); 257 } 258 259 // hook translation passing faces context 260 261 public static String translate(FacesContext context, String messageId) { 262 return translate(context, messageId, (Object[]) null); 263 } 264 265 public static String translate(FacesContext context, String messageId, Object... params) { 266 String bundleName = context.getApplication().getMessageBundle(); 267 Locale locale = context.getViewRoot().getLocale(); 268 return I18NUtils.getMessageString(bundleName, messageId, evaluateParams(context, params), locale); 269 } 270 271 public static void addErrorMessage(FacesContext context, UIComponent component, String message) { 272 addErrorMessage(context, component, message, null); 273 } 274 275 public static void addErrorMessage(FacesContext context, UIComponent component, String message, Object[] params) { 276 String bundleName = context.getApplication().getMessageBundle(); 277 Locale locale = context.getViewRoot().getLocale(); 278 message = I18NUtils.getMessageString(bundleName, message, evaluateParams(context, params), locale); 279 FacesMessage msg = new FacesMessage(message); 280 msg.setSeverity(FacesMessage.SEVERITY_ERROR); 281 context.addMessage(component.getClientId(context), msg); 282 } 283 284 /** 285 * Evaluates parameters to pass to translation methods if they are value expressions. 286 * 287 * @since 5.7 288 */ 289 protected static Object[] evaluateParams(FacesContext context, Object[] params) { 290 if (params == null) { 291 return null; 292 } 293 Object[] res = new Object[params.length]; 294 for (int i = 0; i < params.length; i++) { 295 Object val = params[i]; 296 if (val instanceof String && ComponentTagUtils.isValueReference((String) val)) { 297 ValueExpression ve = context.getApplication().getExpressionFactory().createValueExpression( 298 context.getELContext(), (String) val, Object.class); 299 res[i] = ve.getValue(context.getELContext()); 300 } else { 301 res[i] = val; 302 } 303 } 304 return res; 305 } 306 307 /** 308 * Gets the base naming container from anchor. 309 * <p> 310 * Gets out of suggestion box as it's a naming container and we can't get components out of it with a relative path 311 * => take above first found container. 312 * 313 * @since 5.3.1 314 */ 315 public static UIComponent getBase(UIComponent anchor) { 316 // init base to given component in case there's no naming container for it 317 UIComponent base = anchor; 318 UIComponent container = anchor.getNamingContainer(); 319 if (container != null) { 320 UIComponent supContainer = container.getNamingContainer(); 321 if (supContainer != null) { 322 container = supContainer; 323 } 324 } 325 if (container != null) { 326 base = container; 327 } 328 if (log.isDebugEnabled()) { 329 log.debug(String.format("Resolved base '%s' for anchor '%s'", base.getId(), anchor.getId())); 330 } 331 return base; 332 } 333 334 /** 335 * Returns the component specified by the {@code componentId} parameter from the {@code base} component. 336 * <p> 337 * Does not throw any exception if the component is not found, returns {@code null} instead. 338 * 339 * @since 5.4 340 */ 341 @SuppressWarnings("unchecked") 342 public static <T> T getComponent(UIComponent base, String componentId, Class<T> expectedComponentClass) { 343 if (componentId == null) { 344 log.error("Cannot retrieve component with a null id"); 345 return null; 346 } 347 UIComponent component = ComponentRenderUtils.getComponent(base, componentId); 348 if (component == null) { 349 log.error("Could not find component with id: " + componentId); 350 } else { 351 try { 352 return (T) component; 353 } catch (ClassCastException e) { 354 log.error("Invalid component with id '" + componentId + "': " + component 355 + ", expected a component with interface " + expectedComponentClass); 356 } 357 } 358 return null; 359 } 360 361 static void clearTargetList(UIEditableList targetList) { 362 int rc = targetList.getRowCount(); 363 for (int i = 0; i < rc; i++) { 364 targetList.removeValue(0); 365 } 366 } 367 368 static void addToTargetList(UIEditableList targetList, SelectItem[] items) { 369 for (int i = 0; i < items.length; i++) { 370 targetList.addValue(items[i].getValue()); 371 } 372 } 373 374 /** 375 * Move items up inside the target select 376 */ 377 public static void shiftItemsUp(UISelectMany targetSelect, UISelectItems targetItems, 378 UIEditableList hiddenTargetList) { 379 String[] selected = (String[]) targetSelect.getSelectedValues(); 380 SelectItem[] all = (SelectItem[]) targetItems.getValue(); 381 if (selected == null) { 382 // nothing to do 383 return; 384 } 385 shiftUp(selected, all); 386 targetItems.setValue(all); 387 clearTargetList(hiddenTargetList); 388 addToTargetList(hiddenTargetList, all); 389 } 390 391 public static void shiftItemsDown(UISelectMany targetSelect, UISelectItems targetItems, 392 UIEditableList hiddenTargetList) { 393 String[] selected = (String[]) targetSelect.getSelectedValues(); 394 SelectItem[] all = (SelectItem[]) targetItems.getValue(); 395 if (selected == null) { 396 // nothing to do 397 return; 398 } 399 shiftDown(selected, all); 400 targetItems.setValue(all); 401 clearTargetList(hiddenTargetList); 402 addToTargetList(hiddenTargetList, all); 403 } 404 405 public static void shiftItemsFirst(UISelectMany targetSelect, UISelectItems targetItems, 406 UIEditableList hiddenTargetList) { 407 String[] selected = (String[]) targetSelect.getSelectedValues(); 408 SelectItem[] all = (SelectItem[]) targetItems.getValue(); 409 if (selected == null) { 410 // nothing to do 411 return; 412 } 413 all = shiftFirst(selected, all); 414 targetItems.setValue(all); 415 clearTargetList(hiddenTargetList); 416 addToTargetList(hiddenTargetList, all); 417 } 418 419 public static void shiftItemsLast(UISelectMany targetSelect, UISelectItems targetItems, 420 UIEditableList hiddenTargetList) { 421 String[] selected = (String[]) targetSelect.getSelectedValues(); 422 SelectItem[] all = (SelectItem[]) targetItems.getValue(); 423 if (selected == null) { 424 // nothing to do 425 return; 426 } 427 all = shiftLast(selected, all); 428 targetItems.setValue(all); 429 clearTargetList(hiddenTargetList); 430 addToTargetList(hiddenTargetList, all); 431 } 432 433 /** 434 * Make a new SelectItem[] with items whose ids belong to selected first, preserving inner ordering of selected and 435 * its complement in all. 436 * <p> 437 * Again this assumes that selected is an ordered sub-list of all 438 * </p> 439 * 440 * @param selected ids of selected items 441 * @param all 442 * @return 443 */ 444 static SelectItem[] shiftFirst(String[] selected, SelectItem[] all) { 445 SelectItem[] res = new SelectItem[all.length]; 446 int sl = selected.length; 447 int i = 0; 448 int j = sl; 449 for (SelectItem item : all) { 450 if (i < sl && item.getValue().toString().equals(selected[i])) { 451 res[i++] = item; 452 } else { 453 res[j++] = item; 454 } 455 } 456 return res; 457 } 458 459 /** 460 * Make a new SelectItem[] with items whose ids belong to selected last, preserving inner ordering of selected and 461 * its complement in all. 462 * <p> 463 * Again this assumes that selected is an ordered sub-list of all 464 * </p> 465 * 466 * @param selected ids of selected items 467 * @param all 468 * @return 469 */ 470 static SelectItem[] shiftLast(String[] selected, SelectItem[] all) { 471 SelectItem[] res = new SelectItem[all.length]; 472 int sl = selected.length; 473 int cut = all.length - sl; 474 int i = 0; 475 int j = 0; 476 for (SelectItem item : all) { 477 if (i < sl && item.getValue().toString().equals(selected[i])) { 478 res[cut + i++] = item; 479 } else { 480 res[j++] = item; 481 } 482 } 483 return res; 484 } 485 486 static void swap(Object[] ar, int i, int j) { 487 Object t = ar[i]; 488 ar[i] = ar[j]; 489 ar[j] = t; 490 } 491 492 static void shiftUp(String[] selected, SelectItem[] all) { 493 int pos = -1; 494 for (int i = 0; i < selected.length; i++) { 495 String s = selected[i]; 496 // "pos" is the index of previous "s" 497 int previous = pos; 498 while (!all[++pos].getValue().equals(s)) { 499 } 500 // now current "s" is at "pos" index 501 if (pos > previous + 1) { 502 swap(all, pos, --pos); 503 } 504 } 505 } 506 507 static void shiftDown(String[] selected, SelectItem[] all) { 508 int pos = all.length; 509 for (int i = selected.length - 1; i >= 0; i--) { 510 String s = selected[i]; 511 // "pos" is the index of previous "s" 512 int previous = pos; 513 while (!all[--pos].getValue().equals(s)) { 514 } 515 // now current "s" is at "pos" index 516 if (pos < previous - 1) { 517 swap(all, pos, ++pos); 518 } 519 } 520 } 521 522 /** 523 * Move items from components to others. 524 */ 525 public static void moveItems(UISelectMany sourceSelect, UISelectItems sourceItems, UISelectItems targetItems, 526 UIEditableList hiddenTargetList, boolean setTargetIds) { 527 String[] selected = (String[]) sourceSelect.getSelectedValues(); 528 if (selected == null) { 529 // nothing to do 530 return; 531 } 532 List<String> selectedList = Arrays.asList(selected); 533 534 SelectItem[] all = (SelectItem[]) sourceItems.getValue(); 535 List<SelectItem> toMove = new ArrayList<SelectItem>(); 536 List<SelectItem> toKeep = new ArrayList<SelectItem>(); 537 List<String> hiddenIds = new ArrayList<String>(); 538 if (all != null) { 539 for (SelectItem item : all) { 540 String itemId = item.getValue().toString(); 541 if (selectedList.contains(itemId)) { 542 toMove.add(item); 543 } else { 544 toKeep.add(item); 545 if (!setTargetIds) { 546 hiddenIds.add(itemId); 547 } 548 } 549 } 550 } 551 // reset left values 552 sourceItems.setValue(toKeep.toArray(new SelectItem[] {})); 553 sourceSelect.setSelectedValues(new Object[0]); 554 555 // change right values 556 List<SelectItem> newSelectItems = new ArrayList<SelectItem>(); 557 SelectItem[] oldSelectItems = (SelectItem[]) targetItems.getValue(); 558 if (oldSelectItems == null) { 559 newSelectItems.addAll(toMove); 560 } else { 561 newSelectItems.addAll(Arrays.asList(oldSelectItems)); 562 List<String> oldIds = new ArrayList<String>(); 563 for (SelectItem oldItem : oldSelectItems) { 564 String id = oldItem.getValue().toString(); 565 oldIds.add(id); 566 } 567 if (setTargetIds) { 568 hiddenIds.addAll(0, oldIds); 569 } 570 for (SelectItem toMoveItem : toMove) { 571 String id = toMoveItem.getValue().toString(); 572 if (!oldIds.contains(id)) { 573 newSelectItems.add(toMoveItem); 574 if (setTargetIds) { 575 hiddenIds.add(id); 576 } 577 } 578 } 579 } 580 targetItems.setValue(newSelectItems.toArray(new SelectItem[] {})); 581 582 // update hidden values 583 int numValues = hiddenTargetList.getRowCount(); 584 if (numValues > 0) { 585 for (int i = numValues - 1; i > -1; i--) { 586 hiddenTargetList.removeValue(i); 587 } 588 } 589 for (String newHiddenValue : hiddenIds) { 590 hiddenTargetList.addValue(newHiddenValue); 591 } 592 } 593 594 /** 595 * Move items from components to others. 596 */ 597 public static void moveAllItems(UISelectItems sourceItems, UISelectItems targetItems, 598 UIEditableList hiddenTargetList, boolean setTargetIds) { 599 SelectItem[] all = (SelectItem[]) sourceItems.getValue(); 600 List<SelectItem> toMove = new ArrayList<SelectItem>(); 601 List<SelectItem> toKeep = new ArrayList<SelectItem>(); 602 List<String> hiddenIds = new ArrayList<String>(); 603 if (all != null) { 604 for (SelectItem item : all) { 605 if (!item.isDisabled()) { 606 toMove.add(item); 607 } else { 608 toKeep.add(item); 609 } 610 } 611 } 612 // reset left values 613 sourceItems.setValue(toKeep.toArray(new SelectItem[] {})); 614 615 // change right values 616 List<SelectItem> newSelectItems = new ArrayList<SelectItem>(); 617 SelectItem[] oldSelectItems = (SelectItem[]) targetItems.getValue(); 618 if (oldSelectItems == null) { 619 newSelectItems.addAll(toMove); 620 } else { 621 newSelectItems.addAll(Arrays.asList(oldSelectItems)); 622 List<String> oldIds = new ArrayList<String>(); 623 for (SelectItem oldItem : oldSelectItems) { 624 String id = oldItem.getValue().toString(); 625 oldIds.add(id); 626 } 627 if (setTargetIds) { 628 hiddenIds.addAll(0, oldIds); 629 } 630 for (SelectItem toMoveItem : toMove) { 631 String id = toMoveItem.getValue().toString(); 632 if (!oldIds.contains(id)) { 633 newSelectItems.add(toMoveItem); 634 if (setTargetIds) { 635 hiddenIds.add(id); 636 } 637 } 638 } 639 } 640 targetItems.setValue(newSelectItems.toArray(new SelectItem[] {})); 641 642 // update hidden values 643 int numValues = hiddenTargetList.getRowCount(); 644 if (numValues > 0) { 645 for (int i = numValues - 1; i > -1; i--) { 646 hiddenTargetList.removeValue(i); 647 } 648 } 649 for (String newHiddenValue : hiddenIds) { 650 hiddenTargetList.addValue(newHiddenValue); 651 } 652 } 653 654 public static String verifyTarget(String toVerify, String defaultTarget) { 655 if (StringUtils.isBlank(toVerify)) { 656 return null; 657 } 658 FacesContext context = FacesContext.getCurrentInstance(); 659 boolean ajaxRequest = context.getPartialViewContext().isAjaxRequest(); 660 if (ajaxRequest) { 661 // ease up ajax re-rendering in case of js scripts parsing defer 662 return null; 663 } 664 return defaultTarget; 665 } 666 667 public static String NUXEO_RESOURCE_RELOCATED = "NUXEO_RESOURCE_RELOCATED_MARKER"; 668 669 /** 670 * Marks given component as relocated, so that subsequent calls to {@link #isRelocated(UIComponent)} returns true. 671 * 672 * @since 8.1 673 */ 674 public static void setRelocated(UIComponent component) { 675 component.getAttributes().put(NUXEO_RESOURCE_RELOCATED, "true"); 676 } 677 678 /** 679 * Returns true if given component is marked as relocated. 680 * 681 * @see #setRelocated(UIComponent) 682 * @see #relocate(UIComponent, String, String) 683 * @since 8.1 684 */ 685 public static boolean isRelocated(UIComponent component) { 686 return component.getAttributes().containsKey(NUXEO_RESOURCE_RELOCATED); 687 } 688 689 /** 690 * Relocates given component, adding it to the view root resources for given target. 691 * <p> 692 * If given composite key is not null, current composite component client id is saved using this key on the 693 * component attributes, for later reuse. 694 * <p> 695 * Component is also marked as relocated so that subsequent calls to {@link #isRelocated(UIComponent)} returns true. 696 * 697 * @since 8.1 698 */ 699 public static void relocate(UIComponent component, String target, String compositeKey) { 700 FacesContext context = FacesContext.getCurrentInstance(); 701 if (compositeKey != null) { 702 // We're checking for a composite component here as if the resource 703 // is relocated, it may still require it's composite component context 704 // in order to properly render. Store it for later use by 705 // encodeBegin() and encodeEnd(). 706 UIComponent cc = UIComponent.getCurrentCompositeComponent(context); 707 if (cc != null) { 708 component.getAttributes().put(compositeKey, cc.getClientId(context)); 709 } 710 } 711 // avoid relocating resources that are not actually rendered 712 if (isRendered(component)) { 713 setRelocated(component); 714 context.getViewRoot().addComponentResource(context, component, target); 715 } 716 } 717 718 protected static boolean isRendered(UIComponent component) { 719 UIComponent comp = component; 720 while (comp.isRendered()) { 721 UIComponent parent = comp.getParent(); 722 if (parent == null) { 723 // reached root 724 return true; 725 } else { 726 comp = parent; 727 } 728 } 729 return false; 730 } 731 732}