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