001/* 002 * (C) Copyright 2007-2008 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 * 019 * $Id: Functions.java 28572 2008-01-08 14:40:44Z fguillaume $ 020 */ 021 022package org.nuxeo.ecm.platform.ui.web.tag.fn; 023 024import java.text.DateFormat; 025import java.text.SimpleDateFormat; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.Date; 029import java.util.HashMap; 030import java.util.Iterator; 031import java.util.List; 032import java.util.Locale; 033import java.util.Map; 034import java.util.regex.Matcher; 035import java.util.regex.Pattern; 036 037import javax.faces.component.UIComponent; 038import javax.faces.context.FacesContext; 039import javax.servlet.http.HttpServletRequest; 040 041import org.apache.commons.io.FilenameUtils; 042import org.apache.commons.text.StringEscapeUtils; 043import org.apache.commons.lang3.StringUtils; 044import org.nuxeo.common.utils.i18n.I18NUtils; 045import org.nuxeo.ecm.core.api.NuxeoGroup; 046import org.nuxeo.ecm.core.api.NuxeoPrincipal; 047import org.nuxeo.ecm.core.api.security.SecurityConstants; 048import org.nuxeo.ecm.platform.ui.web.rest.RestHelper; 049import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService; 050import org.nuxeo.ecm.platform.ui.web.util.BaseURL; 051import org.nuxeo.ecm.platform.ui.web.util.ComponentRenderUtils; 052import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 053import org.nuxeo.ecm.platform.url.DocumentViewImpl; 054import org.nuxeo.ecm.platform.url.api.DocumentView; 055import org.nuxeo.ecm.platform.usermanager.UserManager; 056import org.nuxeo.runtime.api.Framework; 057import org.nuxeo.runtime.services.config.ConfigurationService; 058 059/** 060 * Util functions. 061 * 062 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 063 * @author <a href="mailto:tm@nuxeo.com">Thierry Martins</a> 064 */ 065public final class Functions { 066 067 public static final String I18N_DURATION_PREFIX = "label.duration.unit."; 068 069 public static final String BIG_FILE_SIZE_LIMIT_PROPERTY = "org.nuxeo.big.file.size.limit"; 070 071 public static final long DEFAULT_BIG_FILE_SIZE_LIMIT = 5 * 1024 * 1024; 072 073 /** 074 * @since 7.4 075 */ 076 public static final String BYTE_PREFIX_FORMAT_PROPERTY = "nuxeo.jsf.defaultBytePrefixFormat"; 077 078 /** 079 * @since 7.4 080 */ 081 public static final String DEFAULT_BYTE_PREFIX_FORMAT = "SI"; 082 083 public static final Pattern YEAR_PATTERN = Pattern.compile("y+"); 084 085 public enum BytePrefix { 086 087 SI(1000, new String[] { "", "k", "M", "G", "T", "P", "E", "Z", "Y" }, new String[] { "", "kilo", "mega", 088 "giga", "tera", "peta", "exa", "zetta", "yotta" }), IEC(1024, new String[] { "", "Ki", "Mi", "Gi", 089 "Ti", "Pi", "Ei", "Zi", "Yi" }, new String[] { "", "kibi", "mebi", "gibi", "tebi", "pebi", "exbi", 090 "zebi", "yobi" }), JEDEC(1024, new String[] { "", "K", "M", "G" }, new String[] { "", "kilo", "mega", 091 "giga" }); 092 093 private final int base; 094 095 private final String[] shortSuffixes; 096 097 private final String[] longSuffixes; 098 099 BytePrefix(int base, String[] shortSuffixes, String[] longSuffixes) { 100 this.base = base; 101 this.shortSuffixes = shortSuffixes; 102 this.longSuffixes = longSuffixes; 103 } 104 105 public int getBase() { 106 return base; 107 } 108 109 public String[] getShortSuffixes() { 110 return shortSuffixes; 111 } 112 113 public String[] getLongSuffixes() { 114 return longSuffixes; 115 } 116 117 } 118 119 // XXX we should not use a static variable for this cache, but use a cache 120 // at a higher level in the Framework or in a facade. 121 private static UserManager userManager; 122 123 /** 124 * @since 7.2 125 */ 126 private static UserNameResolverHelper userNameResolver = new UserNameResolverHelper(); 127 128 static final Map<String, String> mapOfDateLength = new HashMap<String, String>() { 129 { 130 put("short", String.valueOf(DateFormat.SHORT)); 131 put("shortWithCentury".toLowerCase(), "shortWithCentury".toLowerCase()); 132 put("medium", String.valueOf(DateFormat.MEDIUM)); 133 put("long", String.valueOf(DateFormat.LONG)); 134 put("full", String.valueOf(DateFormat.FULL)); 135 } 136 137 private static final long serialVersionUID = 8465772256977862352L; 138 }; 139 140 // Utility class. 141 private Functions() { 142 } 143 144 public static Object test(Boolean test, Object onSuccess, Object onFailure) { 145 return test ? onSuccess : onFailure; 146 } 147 148 public static String join(String[] list, String separator) { 149 return StringUtils.join(list, separator); 150 } 151 152 public static String joinCollection(Collection<Object> collection, String separator) { 153 if (collection == null) { 154 return null; 155 } 156 return StringUtils.join(collection.iterator(), separator); 157 } 158 159 public static String htmlEscape(String data) { 160 return StringEscapeUtils.escapeHtml4(data); 161 } 162 163 /** 164 * Escapes a given string to be used in a JavaScript function (escaping single quote characters for instance). 165 * 166 * @since 5.4.2 167 */ 168 public static String javaScriptEscape(String data) { 169 if (data != null) { 170 data = StringEscapeUtils.escapeEcmaScript(data); 171 } 172 return data; 173 } 174 175 /** 176 * Can be used in order to produce something like that "Julien, Alain , Thierry et Marc-Aurele" where ' , ' and ' et 177 * ' is the final one. 178 */ 179 public static String joinCollectionWithFinalDelimiter(Collection<Object> collection, String separator, 180 String finalSeparator) { 181 return joinArrayWithFinalDelimiter(collection.toArray(), separator, finalSeparator); 182 } 183 184 public static String joinArrayWithFinalDelimiter(Object[] collection, String separator, String finalSeparator) { 185 if (collection == null) { 186 return null; 187 } 188 StringBuffer result = new StringBuffer(); 189 result.append(StringUtils.join(collection, separator)); 190 if (collection != null && collection.length > 0 && !StringUtils.isBlank(finalSeparator)) { 191 result.append(finalSeparator); 192 } 193 return result.toString(); 194 } 195 196 public static String formatDateUsingBasicFormatter(Date date) { 197 return formatDate(date, basicDateFormatter()); 198 } 199 200 /** 201 * Formats a date using the pattern "EEEE, MMMM d, yyyy h:mm:ss a z". This pattern is the US one and is 202 * being used for consistency since it changes through the various locales 203 * 204 * @since 10.1 205 */ 206 public static String formatDateUsingFullDateAndTimeFormatter(Date date) { 207 // We're using the US one for consistency. 208 String dateFormat = "EEEE, MMMM d, yyyy h:mm:ss a z"; 209 return formatDate(date, dateFormat); 210 } 211 212 public static String formatDate(Date date, String format) { 213 FacesContext context = FacesContext.getCurrentInstance(); 214 Locale locale = context.getViewRoot().getLocale(); 215 216 return new SimpleDateFormat(format, locale) 217 .format(date); 218 } 219 220 public static String concat(String s1, String s2) { 221 return s1 + s2; 222 } 223 224 public static String indentString(Integer level, String text) { 225 StringBuilder label = new StringBuilder(""); 226 for (int i = 0; i < level; i++) { 227 label.append(text); 228 } 229 return label.toString(); 230 } 231 232 public static boolean userIsMemberOf(String groupName) { 233 FacesContext context = FacesContext.getCurrentInstance(); 234 NuxeoPrincipal principal = (NuxeoPrincipal) context.getExternalContext().getUserPrincipal(); 235 return principal.isMemberOf(groupName); 236 } 237 238 private static UserManager getUserManager() { 239 if (userManager == null) { 240 userManager = Framework.getService(UserManager.class); 241 } 242 return userManager; 243 } 244 245 /** 246 * Returns the full name of a user, or its username if user if not found. 247 * <p> 248 * Since 5.5, returns null if given username is null (instead of returning the current user full name). 249 */ 250 public static String userFullName(String username) { 251 if (SecurityConstants.SYSTEM_USERNAME.equals(username)) { 252 // avoid costly and useless calls to the user directory 253 return username; 254 } 255 256 // empty user name is current user 257 if (StringUtils.isBlank(username)) { 258 return null; 259 } 260 return userNameResolver.getUserFullName(username); 261 } 262 263 /** 264 * Returns the full name of a group from his id 265 * 266 * @see #groupDisplayName(String, String) 267 * @param groupId the group id 268 * @return the group full name 269 * @since 5.5 270 */ 271 public static String groupFullName(String groupId) { 272 NuxeoGroup group = getUserManager().getGroup(groupId); 273 String groupLabel = group.getLabel(); 274 String groupName = group.getName(); 275 return groupDisplayName(groupName, groupLabel); 276 } 277 278 // this should be a method of the principal itself 279 public static String principalFullName(NuxeoPrincipal principal) { 280 String first = principal.getFirstName(); 281 String last = principal.getLastName(); 282 return userDisplayName(principal.getName(), first, last); 283 } 284 285 public static String userDisplayName(String id, String first, String last) { 286 if (first == null || first.length() == 0) { 287 if (last == null || last.length() == 0) { 288 return id; 289 } else { 290 return last; 291 } 292 } else { 293 if (last == null || last.length() == 0) { 294 return first; 295 } else { 296 return first + ' ' + last; 297 } 298 } 299 } 300 301 /** 302 * Return, from the id, the id its-self if neither last name nor name are found or the full name plus the email if 303 * this one exists 304 * 305 * @param id id of the user 306 * @param first first name of the user 307 * @param last last name of the user 308 * @param email email of the user 309 * @return id or full name with email if exists 310 * @since 5.5 311 */ 312 public static String userDisplayNameAndEmail(String id, String first, String last, String email) { 313 String userDisplayedName = userDisplayName(id, first, last); 314 if (userDisplayedName.equals(id)) { 315 return userDisplayedName; 316 } 317 if (email == null || email.length() == 0) { 318 return userDisplayedName; 319 } 320 return userDisplayedName + " " + email; 321 } 322 323 /** 324 * Choose between label or name the best string to display a group 325 * 326 * @param name the group name 327 * @param label the group name 328 * @return label if not empty or null, otherwise group name 329 * @since 5.5 330 */ 331 public static String groupDisplayName(String name, String label) { 332 return StringUtils.isBlank(label) ? name : label; 333 } 334 335 /** 336 * Return the date format to handle date taking the user's locale into account. 337 * 338 * @since 5.9.1 339 */ 340 public static String dateFormatter(String formatLength) { 341 // A map to store temporary available date format 342 FacesContext context = FacesContext.getCurrentInstance(); 343 Locale locale = context.getViewRoot().getLocale(); 344 345 int style = DateFormat.SHORT; 346 String styleString = mapOfDateLength.get(formatLength.toLowerCase()); 347 boolean addCentury = false; 348 if ("shortWithCentury".toLowerCase().equals(styleString)) { 349 addCentury = true; 350 } else { 351 style = Integer.parseInt(styleString); 352 } 353 354 DateFormat aDateFormat = DateFormat.getDateInstance(style, locale); 355 356 // Cast to SimpleDateFormat to make "toPattern" method available 357 SimpleDateFormat format = (SimpleDateFormat) aDateFormat; 358 359 // return the date pattern 360 String pattern = format.toPattern(); 361 362 if (style == DateFormat.SHORT && addCentury) { 363 // hack to add century on generated pattern 364 pattern = YEAR_PATTERN.matcher(pattern).replaceAll("yyyy"); 365 } 366 return pattern; 367 } 368 369 /** 370 * Return the date format to handle date taking the user's locale into account. Uses the pseudo "shortWithCentury" 371 * format. 372 * 373 * @since 5.9.1 374 */ 375 public static String basicDateFormatter() { 376 return dateFormatter("shortWithCentury"); 377 } 378 379 /** 380 * Return the date format to handle date and time taking the user's locale into account. 381 * 382 * @since 5.9.1 383 */ 384 public static String dateAndTimeFormatter(String formatLength) { 385 386 // A map to store temporary available date format 387 388 FacesContext context = FacesContext.getCurrentInstance(); 389 Locale locale = context.getViewRoot().getLocale(); 390 391 int style = DateFormat.SHORT; 392 String styleString = mapOfDateLength.get(formatLength.toLowerCase()); 393 boolean addCentury = false; 394 if ("shortWithCentury".toLowerCase().equals(styleString)) { 395 addCentury = true; 396 } else { 397 style = Integer.parseInt(styleString); 398 } 399 400 DateFormat aDateFormat = DateFormat.getDateTimeInstance(style, style, locale); 401 402 // Cast to SimpleDateFormat to make "toPattern" method available 403 SimpleDateFormat format = (SimpleDateFormat) aDateFormat; 404 405 // return the date pattern 406 String pattern = format.toPattern(); 407 408 if (style == DateFormat.SHORT && addCentury) { 409 // hack to add century on generated pattern 410 pattern = YEAR_PATTERN.matcher(pattern).replaceAll("yyyy"); 411 } 412 return pattern; 413 } 414 415 /** 416 * Return the date format to handle date and time taking the user's locale into account. Uses the pseudo 417 * "shortWithCentury" format. 418 * 419 * @since 5.9.1 420 */ 421 public static String basicDateAndTimeFormatter() { 422 return dateAndTimeFormatter("shortWithCentury"); 423 } 424 425 /** 426 * Returns the default byte prefix. 427 * 428 * @since 7.4 429 */ 430 public static BytePrefix getDefaultBytePrefix() { 431 ConfigurationService configurationService = Framework.getService(ConfigurationService.class); 432 return BytePrefix.valueOf( 433 configurationService.getProperty(BYTE_PREFIX_FORMAT_PROPERTY, DEFAULT_BYTE_PREFIX_FORMAT)); 434 } 435 436 public static String printFileSize(String size) { 437 return printFormatedFileSize(size, getDefaultBytePrefix().name(), true); 438 } 439 440 public static String printFormatedFileSize(String sizeS, String format, Boolean isShort) { 441 long size = (sizeS == null || "".equals(sizeS)) ? 0 : Long.parseLong(sizeS); 442 BytePrefix prefix = Enum.valueOf(BytePrefix.class, format); 443 int base = prefix.getBase(); 444 String[] suffix = isShort ? prefix.getShortSuffixes() : prefix.getLongSuffixes(); 445 int ex = 0; 446 while (size > base - 1 || ex > suffix.length) { 447 ex++; 448 size /= base; 449 } 450 451 FacesContext context = FacesContext.getCurrentInstance(); 452 String msg; 453 if (context != null) { 454 String bundleName = context.getApplication().getMessageBundle(); 455 Locale locale = context.getViewRoot().getLocale(); 456 msg = I18NUtils.getMessageString(bundleName, "label.bytes.suffix", null, locale); 457 if ("label.bytes.suffix".equals(msg)) { 458 // Set default value if no message entry found 459 msg = "B"; 460 } 461 } else { 462 // No faces context, set default value 463 msg = "B"; 464 } 465 466 return "" + size + " " + suffix[ex] + msg; 467 } 468 469 /** 470 * Format the duration of a media in a string of two consecutive units to best express the duration of a media, 471 * e.g.: 472 * <ul> 473 * <li>1 hr 42 min</li> 474 * <li>2 min 25 sec</li> 475 * <li>10 sec</li> 476 * <li>0 sec</li> 477 * </ul> 478 * 479 * @param durationObj a Float, Double, Integer, Long or String instance representing a duration in seconds 480 * @param i18nLabels a map to translate the days, hours, minutes and seconds labels 481 * @return the formatted string 482 */ 483 public static String printFormattedDuration(Object durationObj, Map<String, String> i18nLabels) { 484 485 if (i18nLabels == null) { 486 i18nLabels = new HashMap<String, String>(); 487 } 488 double duration = 0.0; 489 if (durationObj instanceof Float) { 490 duration = ((Float) durationObj).doubleValue(); 491 } else if (durationObj instanceof Double) { 492 duration = ((Double) durationObj).doubleValue(); 493 } else if (durationObj instanceof Integer) { 494 duration = ((Integer) durationObj).doubleValue(); 495 } else if (durationObj instanceof Long) { 496 duration = ((Long) durationObj).doubleValue(); 497 } else if (durationObj instanceof String) { 498 duration = Double.parseDouble((String) durationObj); 499 } 500 501 int days = (int) Math.floor(duration / (24 * 60 * 60)); 502 int hours = (int) Math.floor(duration / (60 * 60)) - days * 24; 503 int minutes = (int) Math.floor(duration / 60) - days * 24 * 60 - hours * 60; 504 int seconds = (int) Math.floor(duration) - days * 24 * 3600 - hours * 3600 - minutes * 60; 505 506 int[] components = { days, hours, minutes, seconds }; 507 String[] units = { "days", "hours", "minutes", "seconds" }; 508 String[] defaultLabels = { "d", "hr", "min", "sec" }; 509 510 String representation = null; 511 for (int i = 0; i < components.length; i++) { 512 if (components[i] != 0 || i == components.length - 1) { 513 String i18nLabel = i18nLabels.get(I18N_DURATION_PREFIX + units[i]); 514 if (i18nLabel == null) { 515 i18nLabel = defaultLabels[i]; 516 } 517 representation = String.format("%d %s", components[i], i18nLabel); 518 if (i < components.length - 1) { 519 i18nLabel = i18nLabels.get(I18N_DURATION_PREFIX + units[i + 1]); 520 if (i18nLabel == null) { 521 i18nLabel = defaultLabels[i + 1]; 522 } 523 representation += String.format(" %d %s", components[i + 1], i18nLabel); 524 } 525 break; 526 } 527 } 528 return representation; 529 } 530 531 public static String printFormattedDuration(Object durationObj) { 532 return printFormattedDuration(durationObj, null); 533 } 534 535 public static final String translate(String messageId, Object... params) { 536 return ComponentUtils.translate(FacesContext.getCurrentInstance(), messageId, params); 537 } 538 539 /** 540 * @return the big file size limit defined with the property org.nuxeo.big.file.size.limit 541 */ 542 public static long getBigFileSizeLimit() { 543 return getFileSize(Framework.getProperty(BIG_FILE_SIZE_LIMIT_PROPERTY, "")); 544 } 545 546 public static long getFileSize(String value) { 547 Pattern pattern = Pattern.compile("([1-9][0-9]*)([kmgi]*)", Pattern.CASE_INSENSITIVE); 548 Matcher m = pattern.matcher(value.trim()); 549 long number; 550 String multiplier; 551 if (!m.matches()) { 552 return DEFAULT_BIG_FILE_SIZE_LIMIT; 553 } 554 number = Long.valueOf(m.group(1)); 555 multiplier = m.group(2); 556 return getValueFromMultiplier(multiplier) * number; 557 } 558 559 /** 560 * Transform the parameter in entry according to these unit systems: 561 * <ul> 562 * <li>SI prefixes: k/M/G for kilo, mega, giga</li> 563 * <li>IEC prefixes: Ki/Mi/Gi for kibi, mebi, gibi</li> 564 * </ul> 565 * 566 * @param m : binary prefix multiplier 567 * @return the value of the multiplier as a long 568 */ 569 public static long getValueFromMultiplier(String m) { 570 if ("k".equalsIgnoreCase(m)) { 571 return 1L * 1000; 572 } else if ("Ki".equalsIgnoreCase(m)) { 573 return 1L << 10; 574 } else if ("M".equalsIgnoreCase(m)) { 575 return 1L * 1000 * 1000; 576 } else if ("Mi".equalsIgnoreCase(m)) { 577 return 1L << 20; 578 } else if ("G".equalsIgnoreCase(m)) { 579 return 1L * 1000 * 1000 * 1000; 580 } else if ("Gi".equalsIgnoreCase(m)) { 581 return 1L << 30; 582 } else { 583 return 1L; 584 } 585 } 586 587 /** 588 * Returns true if the faces context holds messages for given JSF component id, usually the form id. 589 * <p> 590 * Id given id is null, returns true if there is at least one client id with messages. 591 * <p> 592 * Since the form id might be prefixed with a container id in some cases, the method returns true if one of client 593 * ids with messages stats with given id, or if given id is contained in it. 594 * 595 * @since 5.4.2 596 */ 597 public static boolean hasMessages(String clientId) { 598 Iterator<String> it = FacesContext.getCurrentInstance().getClientIdsWithMessages(); 599 if (clientId == null) { 600 return it.hasNext(); 601 } else { 602 while (it.hasNext()) { 603 String id = it.next(); 604 if (id != null 605 && (id.startsWith(clientId + ":") || id.contains(":" + clientId + ":") || id.equals(clientId) || id.endsWith(":" 606 + clientId))) { 607 return true; 608 } 609 } 610 } 611 return false; 612 } 613 614 public static String userUrl(String patternName, String username, String viewId, boolean newConversation) { 615 return userUrl(patternName, username, viewId, newConversation, null); 616 } 617 618 public static String userUrl(String patternName, String username, String viewId, boolean newConversation, 619 HttpServletRequest req) { 620 Map<String, String> parameters = new HashMap<String, String>(); 621 parameters.put("username", username); 622 DocumentView docView = new DocumentViewImpl(null, viewId, parameters); 623 624 // generate url 625 URLPolicyService service = Framework.getService(URLPolicyService.class); 626 if (patternName == null || patternName.length() == 0) { 627 patternName = service.getDefaultPatternName(); 628 } 629 630 String baseURL = null; 631 if (req == null) { 632 baseURL = BaseURL.getBaseURL(); 633 } else { 634 baseURL = BaseURL.getBaseURL(req); 635 } 636 637 String url = service.getUrlFromDocumentView(patternName, docView, baseURL); 638 639 // pass conversation info if needed 640 if (!newConversation && url != null) { 641 url = RestHelper.addCurrentConversationParameters(url); 642 } 643 644 return url; 645 } 646 647 public static List<Object> combineLists(List<? extends Object>... lists) { 648 List<Object> combined = new ArrayList<Object>(); 649 for (List<? extends Object> list : lists) { 650 combined.addAll(list); 651 } 652 return combined; 653 } 654 655 /** 656 * Helper that escapes a string used as a JSF tag id: this is useful to replace characters that are not handled 657 * correctly in JSF context. 658 * <p> 659 * This method currently removes ASCII characters from the given string, and replaces "-" characters by "_" because 660 * the dash is an issue for forms rendered in ajax (see NXP-10793). 661 * <p> 662 * Also starting digits are replaced by the "_" character because a tag id cannot start with a digit. 663 * 664 * @since 5.7 665 * @return the escaped string 666 */ 667 public static String jsfTagIdEscape(String base) { 668 if (base == null) { 669 return null; 670 } 671 int n = base.length(); 672 StringBuilder res = new StringBuilder(); 673 for (int i = 0; i < n; i++) { 674 char c = base.charAt(i); 675 if (i == 0) { 676 if (!Character.isLetter(c) && (c != '_')) { 677 res.append("_"); 678 } else { 679 res.append(c); 680 } 681 } else { 682 if (!Character.isLetter(c) && !Character.isDigit(c) && (c != '_')) { 683 res.append("_"); 684 } else { 685 res.append(c); 686 } 687 } 688 } 689 return org.nuxeo.common.utils.StringUtils.toAscii(res.toString()); 690 } 691 692 /** 693 * Returns the extension from the given {@code filename}. 694 * <p> 695 * See {@link FilenameUtils#getExtension(String)}. 696 * 697 * @since 5.7 698 */ 699 public static String fileExtension(String filename) { 700 return FilenameUtils.getExtension(filename); 701 } 702 703 /** 704 * Returns the base name from the given {@code filename}. 705 * <p> 706 * See {@link FilenameUtils#getBaseName(String)}. 707 * 708 * @since 5.7 709 */ 710 public static String fileBaseName(String filename) { 711 return FilenameUtils.getBaseName(filename); 712 } 713 714 /** 715 * Joins two strings to get a valid render attribute for ajax components. 716 * 717 * @since 6.0 718 */ 719 public static String joinRender(String render1, String render2) { 720 if (StringUtils.isBlank(render1) && StringUtils.isBlank(render2)) { 721 return ""; 722 } 723 String res; 724 if (StringUtils.isBlank(render1)) { 725 res = render2; 726 } else if (StringUtils.isBlank(render2)) { 727 res = render1; 728 } else { 729 res = StringUtils.join(new String[] { render1, render2 }, " "); 730 res = res.replaceAll("\\s+", " "); 731 } 732 res = res.trim(); 733 return res; 734 } 735 736 /** 737 * Returns the target component absolute id given an anchor in the tree and a local id. 738 * <p> 739 * If given targetId parameter contains spaces, consider several ids should be resolved and split them. 740 * 741 * @since 6.0 742 * @param anchor the component anchor, used a localization for the target component in the tree. 743 * @param targetId the component to look for locally so as to return its absolute client id. 744 */ 745 public static String componentAbsoluteId(UIComponent anchor, String targetId) { 746 // handle case where several target ids could be given as input 747 if (targetId == null) { 748 return null; 749 } 750 if (targetId.contains(" ")) { 751 String res = ""; 752 for (String t : targetId.split(" ")) { 753 res = joinRender(res, ComponentRenderUtils.getComponentAbsoluteId(anchor, t.trim())); 754 } 755 return res; 756 } else { 757 return ComponentRenderUtils.getComponentAbsoluteId(anchor, targetId); 758 } 759 } 760}